Screening as a specialised annotation: code-grounded investigation¶
Current DP6 correction: personal Include governs within-stage steps; cross-stage routing is configurable. DP7 confirms Collective Include required as cross-stage default; advanced own-Include option allowed. Strict within-stage details remain proposed. Older blanket cross-stage DP1 wording below is historical/superseded.
Scope and recommendation¶
3 October reason-reconciliation clarification (DP5): profile On/Off controls whether exclusion reasons require reconciliation. Off retains candidate reasons/history and separate decision resolution. Older no-bypass defaults apply only within the applicable enabled policy; no hard dependency on reason collection or forced setting change is approved.
3 October ownership correction (DP4): screening eligibility questions/configuration belong to their screening profile, distinct from ordinary study facts. Templates copy into profile-local definitions; template edits do not silently update existing profiles. Same-profile stages share profile-scoped answers; importing into separate profiles does not share answers. Reuse question-tree infrastructure without conflating ownership. This supersedes older generic project-asset wording for screening definitions; ordinary study-fact sharing remains unchanged. See the current owner ledger.
Later reconciliation acceptance (RE2): final reconciliation form submission accepts valid displayed answers including matching prefill, with obvious populated indicators and an unseen-control warning offering Complete anyway and source provenance. No per-field confirmation or gold promotion before submission; Save/autosave unfinished. Query child validity and profile screening rules remain unchanged.
Later export/statistics-access decisions: EX1 defaults downloads to current answers and requires previous versions/date-specific review state for reproducibility. AG1 is a separate agreement-statistics view capability without admin control, candidate-answer exposure or identity-blinding bypass. Reuse suitable existing grants and recovered #2461/#2574 groundwork; historical mode implementation is not verified. See owner ledger and read-only investigation.
Later explanation correction (RE1): reconciled annotation-answer explanation is optional even when differing from every candidate; reminders are non-blocking, never a completion/ publication gate. This supersedes older rationale-requiredness recommendations below. EX1 confirms current/historical/as-of downloads; Reconcile is separate from extra-review permission.
Later 2 October workflow/permission decisions: EW1 is stage default Allow with advanced per-step override; BL1 identity blinding is stage-owned. RA1–RA5 keep pool default, permit eligible explicit admin assignment, scope expiry to unstarted explicit work, and require release/reacquire for started work. Additional independent review needs its own capability. See the owner ledger and permission inventory; older placement/expiry/ role assumptions below are historical where they conflict. No runtime changes are authorised.
Later 2 October update/query decisions: VU1–VU3 and QY1–QY7 in the linked owner ledger supersede earlier proposal-only query treatment. Needs-updating answers remain visible; version reason/guidance are optional. Pending accepted gold stays effective, per-version grouped concerns retain individual resolution, affected children are resolved before replacement gold, authorised query self-review is allowed/audited, rejection explanation optional, existing viewers may query, and resolution notifications preserve isolation. Stale-version/concurrent handling remains open. Publish-pause retry/notification UX is only a recommendation. No runtime/contact authorization.
Later 2 October lifecycle correction (SL3): the latest explicit Save OR Complete is the current form-session version. Incomplete Save replaces completed status and no longer counts as completed; autosaved changes alone remain unconfirmed edits over the explicit version. Retain immutable history, separate draft indicators and publication treatment for completed, saved incomplete and draft-only sessions. This supersedes any earlier effective-completed- until-Complete assumption below. It does not silently change gold or screening decisions. Existing admin-confirmed requireReanswer/autoUpdate/doNothing choices are recovered from Annotation Versioning §3.1–3.3, not a new owner decision. Their application to shared saved incomplete/draft work and confirmation UX still need design; see the linked decision ledger. Older added-question “no impact” wording is superseded by FV1–FV3.
2 October owner-decision overlay: read the shared-form decision record and updated review-step handoff before using the older examples below. Chris confirmed shared study/form reviewer sessions, form-owned shared targets, compatible answers across overlapping forms, revision-level provenance, versioned stage bindings, autosave versus immutable Save/Complete, and configurable accepted-gold visibility with distinct independent/informed agreement reporting. Form publication checks any prior-version sessions; admin treatment controls qualification against updated requirements. Form/question impact uses materialized usage made current at a protected publish boundary; protocol specifics remain to propose with the statistics owner. These later decisions supersede conflicting per-step target/ session and blanket gold-visibility assumptions; historical engineering examples remain research, not current owner decisions. EW1 stage default Allow and advanced per-step override are confirmed.
25 September design discussion: the review-step prototype handoff records later decisions on multi-step sessions, separate contribution counts, selection, collective prerequisite satisfaction, versioned surplus assessments and form submissions. It also supersedes the retained FEAT-009 defaults below where explicitly stated: choice-based agreement with free text not required to match by default, and candidate collective Exclude blocking dependent new work while required reason reconciliation remains pending. These are design decisions, not implemented behaviour. This investigation's older single-binding and session-level examples require adaptation to that review-step model.
Expanded integrated-model research (24 September): read the unified annotation and cohort-classification proposal alongside this screening specification. It incorporates the recovered 13 August cohort-design conversation, reusable Definition Types and Classification capability, custom references, metadata/response modes, cohort coverage and subset reasoning, configurable outcome-data schemas, quantitative counts, shared observations, and the combined U0–U8 rollout. Cohort size already is a system-question annotation; the integrated proposal preserves that pattern for role-bound quantitative values. The clarified outcome-data model uses project-owned schema definitions selected through a schema-reference annotation question. Stages select project questions; reviewers create paper-specific outcomes and their answers, then enter cohort-linked observations in Experiments. Units and average/error types remain linked outcome annotations. The companion separates this domain requirement from its tentative MongoDB collection layout and question-class choices. It distinguishes verified code from historical proposals and new recommendations. The Windows-created source files were located in conversation history but not directly read.
Research and proposed design, not implementation approval. Inspected on Juniper against
SyRF commit c30ec96e625d18f642151e2fd2c6ca7e6caa6cd0 (the main checkout at investigation
start). Source links below are pinned to that commit. No production data or deployment
configuration was queried; nothing here establishes deployed behaviour or historical data
prevalence. No runtime changes, data migrations, merges or deployments were performed.
The separate materialised-statistics task was not resumed or modified.
Recommended end state: one Annotation domain concept, with ordinary answers and screening decisions as its initial distinct kinds. Both have stable identity, immutable revisions, author/actor provenance, explicit ownership/context, typed content, owned child annotations and submitted snapshot references. Screening is a specialised annotation because it adds a profile-scoped natural key, effective-vote rules, agreement, eligibility and specific command permissions. A collective outcome is a derived result, not another reviewer annotation or an extra vote. Stage sessions organise work and pin submissions; they do not own a second copy of every annotation or determine vote cardinality. The purpose is to remove duplicated, rigid identity/history/submission infrastructure while retaining enforceable scientific policies; sharing a renderer alone does not meet that goal.
This specification defines that complete destination now. Incremental delivery determines
when capabilities become available, not whether the unified model will be designed.
Composition of shared contracts and specialised policies is the recommended implementation
of this conceptual model; inheriting from a redesigned base is also possible. Merely keeping
screening separate and sharing its renderer is an interim adapter, not the target architecture.
The existing Annotation class is not itself the target common base.
The first independently useful slice is a narrow versioned ordinary-annotation path proving the shared identity/revision/submission contract; explicit-profile screening then adds the second kind using that same contract. Acceptance for that first slice: a reviewer can save, complete, reopen and revise a study answer, while an earlier completed submission and its export remain unchanged and existing projects continue to work. The shortest critical path is shared identity/context → revision and submission transaction → existing form adapter → read/history/export. The first screening slice adds profile binding → specialised decision command → outcome/selection/counts → explicit Include/Exclude UI. This is deliberately a thin implementation sequence toward the defined end state, not permission to defer its model.
Sections 3 and 6 specify the destination and its tradeoffs; section 7 maps current structures to that destination, names temporary adapters and their removal milestones, and supplies acceptance gates. No milestone is approved or underway.
This is an In-Review long-term specification and planning investigation. Move accepted contracts into feature docs/ADRs with their implementation PRs; do not silently promote this document to Approved.
1. Verified current behaviour¶
1.1 Screening identity, aggregation and history¶
Screening and Annotation are separate Entity<Guid> descendants. A Screening has
study, project, screener, stage and decision fields. ScreeningInfo.ScreenStudy finds an
existing record by project + screener within the Study, then changes its decision and
StageId. It does not append a second vote when the reviewer works in another stage.
Duplicate matching records cause an exception. The study itself supplies study identity.
There is no profile ID on this record. Screening model · ScreenStudy
The current record is mutable, not a revision ledger. The original entity ID survives a correction, but this path stores neither the prior decision nor its prior stage. An entity creation timestamp is not evidence of when a later correction occurred. Logs or backups might contain additional evidence, but this investigation did not inspect them and does not assume they can reconstruct history. ChangeScreeningDecision
Agreement is calculated from ScreeningInfo.Screenings, independently of annotation
sessions. If n is the number of decisions and i the number of Includes, inclusion is
i/n and absolute agreement is 2 * abs(i/n - 0.5). No decisions gives null inclusion.
The configured threshold is on Project, not a screening-profile entity. Single screening
uses (0,1), manual dual (null,2), automated dual (0.333,2). The implementation uses
its actual floating-point and strict-comparison semantics; do not replace it with an
assumed unanimity rule during compatibility work. Agreement calculation ·
Threshold semantics
For automated dual: one Include is insufficient; two Includes are Included; Include +
Exclude is still insufficient; a third Include produces Included. For manual dual,
Include + Exclude can be screening-complete but Disagree. Therefore completion,
agreement and eligibility are separate facts. GetScreeningCompleteness and
GetInclusionStatus are separate methods and must both be characterised, including custom
thresholds. Threshold semantics · Threshold tests
InclusionInfo[] is a set of computed results for tracked agreement thresholds applied
to the same decision list. It is not a set of independent stage screening rounds.
Mongo mappings persist inclusion, counts, agreement and inclusion-info for querying;
filters select the matching threshold values. Mapping each array element or each
Screening.StageId to a historical profile would invent information. InclusionInfo ·
Mongo mappings · Threshold filters
1.2 Opening, saving and completing annotations¶
An AnnotationSession records stage, investigator, Incomplete/Completed, and a separate
Reconciliation flag. Candidate sessions are unique per stage + investigator within the
study, while the creation guard allows one reconciliation session per stage irrespective
of reconciler. Session IDs are also used to find and update existing sessions. These are
current-state uniqueness checks, not a history model. Session · Session upsert
Session annotation views select by stage + annotator + reconciled flag. Annotation records
carry their own StageId, QuestionId, ParentId, Children, Root, typed answer, notes
and reconciliation flag. Parent/child IDs express an actual answer tree. They do not
currently express a screening-decision attachment. Annotation · Session views
With active-reviewer tracking, opening work claims a SlotReservation, keyed by investigator + stage within the study. Dirty-form presence is still a reservation. First save creates the session and removes the reservation in the same aggregate mutation, carrying engagement timestamps across. This happens for an incomplete save as well as a completed one. Reservations contribute to allocation, not completed-session counts or screening agreement. Tracking-off paths differ; this is not a claim that every historical open created a reservation. Reservation · Graduation · Tallies
UpdateStatus(Completed) sets a completion timestamp if absent; reopening to Incomplete
clears it. Legacy BSON without engagement timestamps retains null timestamps. Completion
therefore does not provide an immutable record of all prior submissions. Session lifecycle ·
Timestamp tests
AF2 Save progress persists Incomplete. Complete persists Completed and emits completed
only after persistence succeeds; the stage-review host then decides whether to advance.
Its completion handler does not submit Include. AF1 also submits an explicit session
status. The shared server submission service validates annotation relationships and calls
Study.AddSessionData; it does not call AddScreening. Consequently opening, draft
saving, completing and navigating cannot be treated as equivalent lifecycle events.
AF2 lifecycle · AF2 persistence ·
Review host · AF1 save · Submission domain service
1.3 Cross-stage answers and reconciliation¶
The implementation is mixed, rather than purely stage-owned or purely study-owned:
- Annotation objects and session views carry/filter by stage.
ExtractionInfo.AddAnnotationsreplaces answers for the project, relevant stage question IDs, reviewer (unless reconciliation), and reconciled flag; the replacement predicate is not a StageId predicate. Matching incoming annotation IDs also replace old records.- When a shared parent is replaced with the same ID, child answers whose questions are absent from the submitting stage are preserved. Existing tests exercise three-stage round trips, grandchildren, differing reviewers and reconciliation preserving candidates.
- Reconciliation replacement can span reconcilers for the selected questions. Candidate annotations remain separate because of the reconciled flag; a reconciliation session's visible answer set still uses stage and author filtering.
GetAnnotationsForInvestigatoroffers a broader project-scoped selection, but finding that helper is not proof every API uses it. The review DTO resolver returns detailed candidate sessions only for the current reviewer, with other sessions summarized. AF2 reconciliation candidates are explicitly supplied from session annotation-ID lists and the current stage's questions. Review DTO resolver
Replacement and merge code · Session views · Cross-stage tests · AF2 reconciliation source
This already supports aspects of sharing and reconciliation. It does not establish immutable shared answers, context-safe sharing between different screening decisions, or historical completed-session snapshots. Replacing a shared answer can change what an older session exposes; adding decision children to the existing global answer list would make question-based replacement particularly dangerous.
Annotation reconciliation exists today: Reconciliation sessions, completed-candidate
thresholds in pool filters, and reconciliation UI/data-source paths are implemented.
By contrast, the screening /reconcile endpoint still calls the ordinary screening
mutation, passing a reconciliation flag that affects guards/reservation handling. The
Screening entity has no separate adjudication record or flag. Do not migrate these
records as proven authoritative adjudications. Reconciliation pool ·
Screening reconciliation endpoint · Screening submission
1.4 Selection, permissions, capacity and saved work¶
Fresh screening selection requires insufficient screening and no vote by this reviewer. Fresh annotation selection generally uses not collectively Excluded, stage capacity and absence of the applicable saved/completed session. Not Excluded is broader than Included: it can admit insufficiently screened studies. Combined selection has a path for work needing either activity. Selection is not proof that every admitted study must or will complete both activities. Selection filters · Fetch dispatch
Current ordinary screening submission rejects a new reviewer decision once project screening is complete, including on retries. Existing-decision corrections and the reconciliation path are exempt from that particular veto. This is the inspected source baseline, not a statement that the approved Allow/Stop proposal is implemented. Sufficiency guard · Sufficiency tests
Stage.ReviewMode and selection mode are distinct; its annotation target defaults to the
project threshold when not explicitly set. Proportional workload shares currently require
annotation-only review and selection modes. Allocation eligibility admits already-saved
candidate work even when a reviewer no longer belongs to its new-work bucket; the submit
service rechecks on a freshly loaded study before mutation. There is no implemented
multi-profile allocation system to reuse unchanged. Stage configuration ·
Allocation eligibility · Save orchestrator · Allocation tests
There is one untyped stage reservation today. Screening capacity counts screenings whose current StageId matches the stage, plus reservations in that stage excluding reviewers already counted there. Earlier-stage votes do not consume that stage's screening capacity, even though effective decision identity is project-wide. Annotation capacity uses stage sessions + reservations. These are materially different counting grains. Screening capacity · Session tally · Reservation
Ordinary screening and session PUT require StageReviewPolicy; screening reconciliation
requires StageReconcilePolicy. Active membership is also checked by the domain submission
service. Stage permission resolution includes project grants and active member grants.
However, session PUT uses Review policy even when the payload says reconciliation;
ReconciliationCheck checks answer flags, not Reconcile permission. Route/payload stage
and session-ID equality is explicitly enforced in the shown proportional-allocation branch,
not as a universal command invariant. These contracts need explicit tests and enforcement
in the new command; a new profile endpoint must not merely trust stage UI visibility or
inherit today's payload semantics. Controller submission ·
Screening endpoints · Reconciliation endpoint · Permissions
1.5 Import, export, queries and statistics¶
The reference-file import pipeline supports explicit reviewer screening columns. It
validates mapped investigator/membership and skips blank cells. Parsed nonblank decisions
call Study.AddScreening using the configured stage. Bulk study updates parse a reviewer
map and likewise apply AddScreening. These paths bypass the interactive controller;
profile routing, revision provenance and outcome calculation must include them.
Blank import cells are no decision, not Exclude or Include.
Screening import · Bulk-update parser · Bulk application · Parser tests
The annotation-import design describes a future versioned import contract. Searches of
current runtime AddAnnotations/AddSessionData callers did not establish that proposed
import service as implemented. Keep it as an integration dependency rather than claiming
that all imported annotations have version/session provenance already.
Annotation-import proposal
Long screening export iterates current screening records and writes entity creation time
and decision; wide export resolves a current decision per project/reviewer, leaving a blank
where absent. Neither is a historical vote export. Long annotation export selects by
question IDs (stage/category question sets), not session completion or annotation StageId.
Wide export can split rows by sessions selected by stage, while ReviewContext selects
study answers by reviewer/reconciled/question identity. OnlyCompleted is carried into
WriterConfig, but the inspected writers do not consume it; repository-wide C# references
in API/project-management showed declaration/forwarding, not a completion filter. Do not
use that option as proof a legacy export contains only completed work.
Screening export · Long rows · Wide screening ·
Annotation export · Wide annotation ·
ReviewContext · WriterConfig
Queries use the persisted ScreeningInfo threshold results and session tallies. Statistics
include project/reviewer screening and stage annotation families, split excluded/unexcluded,
and distinguish candidates started/completed and reconciliation started/completed. A
reservation or incomplete session is not a completed review; a study being absent from
one available-work pool is not evidence it was Excluded. SessionTally.TotalAllocatedSessionCount
adds candidate sessions and reservations; completed candidates are counted separately.
Threshold filters · Statistics · Tallies
SignalR join/leave/dirty/heartbeat operations participate in reservation ownership and presence. First save can split presence records, and non-advancing screening intentionally retains the reservation while annotation remains open. Idle/suspension consumers release reservations, not saved answers. These are state-transition paths, not just UI notifications. Hub · Save orchestrator · Non-advancing screening · Suspended cleanup
1.6 Test evidence and limits¶
Executed on Juniper in this documentation worktree, using existing tests and synthetic fixtures; the MongoDB collection fixture starts an isolated Testcontainers Mongo instance. No production connection was used. Fixture isolation
| Run | Result | What it supports |
|---|---|---|
Core: ScreeningInfoTest, ProjectAgreementThresholdTest, AnnotationSessionTests, ExtractionInfoChildrenMerge*, StudyUpdateRecordProcessorScreeningTests |
50 passed, 0 failed, 0 skipped | Basic screening writes, threshold edge cases, nullable legacy timestamps, reopen behaviour, cross-stage child preservation, blank/invalid import values |
API: OrdinaryScreening_*, SubmitAsync_RechecksAllocationOnFreshRetry, SubmitAsync_DeniesAllocationBeforeFirstMutationWhenSavedSessionAlreadyGone |
20 passed, 0 failed, 0 skipped | New-vote sufficiency refusal, retry recheck, correction exemption, allocation recheck and saved-work access |
| AF2 component specifications | Inspected, not executed | Save failure does not emit completion; read-only candidate forms cannot persist |
Commands (from the worktree root):
dotnet test src/libs/project-management/SyRF.ProjectManagement.Core.Tests/SyRF.ProjectManagement.Core.Tests.csproj --filter 'FullyQualifiedName~ScreeningInfoTest|FullyQualifiedName~ProjectAgreementThresholdTest|FullyQualifiedName~AnnotationSessionTests|FullyQualifiedName~ExtractionInfoChildrenMerge|FullyQualifiedName~StudyUpdateRecordProcessorScreeningTests' --logger 'console;verbosity=minimal'
dotnet test src/services/api/SyRF.API.Endpoint.Tests/SyRF.API.Endpoint.Tests.csproj --no-build --filter 'FullyQualifiedName~OrdinaryScreening_|FullyQualifiedName~SubmitAsync_RechecksAllocationOnFreshRetry|FullyQualifiedName~SubmitAsync_DeniesAllocationBeforeFirstMutationWhenSavedSessionAlreadyGone' --logger 'console;verbosity=minimal'
The API project was built first. An initial filter using filenames matched zero tests
because those files contain partial classes; the corrected method filters above ran 20.
These 70 tests were executed for the initial code-grounded investigation. The later
integrated-model validation
adds execution of 34 existing quantitative-export and 28 existing relationship-validator
tests plus a synthetic set experiment. All changes remain documentation-only; none of these
tests constitutes implementation or runtime coverage of the proposed new model.
Builds emitted existing warnings. ./docs/scripts/validate-docs.sh --verbose passed
with the planning index current (63 warnings in existing documentation);
git diff --cached --check passed. All 57 pinned source/test paths and line numbers and
relative report links were checked locally. No browser/E2E, production-data survey, full-suite or
migration rehearsal was performed. Existing tests do not prove the proposed profile,
immutable-history or atomic combined-submit contracts. AF2 evidence
2. Existing proposals: relevant, not implementation evidence¶
The integrated source ledger adds the recovered cohort-classification requirements/explainer, their later corrections and schema/profile and metadata proposals. Its conservative inference contract requires scope and coverage evidence, not just matching labels or criteria. Screening agreement and cohort inference remain distinct derived policies over the shared annotation/revision infrastructure.
| Document | Useful direction | Qualification from this investigation |
|---|---|---|
| FEAT-007 Screening profiles (In-Review) | Reusable criteria/agreement, separate decision/outcome, immutable-on-use profile | No runtime profile entity found. InclusionInfo is threshold-scoped, not a historical stage/profile array. Do not assign all old decisions to an arbitrary first new profile. No-flag automatic migration is unsuitable for this change. |
| FEAT-009 Screening annotations (In-Review) | Atomic reasons + decision, separate reconciled result, preserve candidates | Its no-partial-save rule is a proposal, not today's session behaviour. Candidate drafts can be saved without effective votes. Its description of data-extraction reconciliation as entirely future must not erase existing annotation reconciliation. |
| Annotation versioning design session | Stable identities, immutable answer/session versions, cross-stage references | These are valuable target concepts, not evidence current sessions pin historical answers. The document itself records conflicting older collection proposals. Use it to define the end state now; stage its implementation without claiming that current storage already supplies those semantics. |
| Reconciliation design (In-Review) | A versioned study-wide authoritative answer set accumulating across stages | Its February 2026 zero-production-record claim is historical proposal context, not current evidence; this investigation did not verify production. Existing runtime reconciliation must be preserved. The older embedded migration draft explicitly requires reconciliation with later designs. |
| Review eligibility policy | Separate capability, selection, collective completion, individual contribution; preserve saved work | Preserve its recorded owner decisions: screening-only strict; existing combined unset → Allow; new combined → Stop; annotation-only has no screening policy. Current source's global new-vote veto is not proof those defaults have shipped. Typed reservation lifecycle remains proposed there. |
| Proportional allocation | Independent stage annotation assignments and saved-work access | Current workload-share configuration restricts stages to annotation-only. A combined-stage allocation guarantee cannot be claimed without another slice. |
| PRISMA specification | Eligibility/reporting context and distinction between processing and scientific decisions | This investigation does not verify the entire PRISMA programme. Never manufacture exclusion counts from pool filtering or assume every lifecycle statement in an overview exists in the current Study class. |
3. Recommended domain model (proposed)¶
3.1 Unified concept first; implementation choice second¶
An Annotation is an attributed, versioned assertion concerning a subject in a project, under a definition and a semantic context. It has two principal kinds:
- Answer: an ordinary typed answer to a versioned question. Its context can be an ordinary study/entity context or a decision-owned evidence context. “Reason” is an Answer's purpose and ownership, not an unrelated third answer system.
- ScreeningDecision: an Include/Exclude assertion against one immutable screening profile. Its definition is the profile's decision definition, not an administrator-created ordinary question that may be deleted or changed to a number. It can own Answer children for reasons and eligibility evidence. It participates in the same annotation revision/tree protocols, plus specialised admission and outcome rules.
Candidate versus Reconciled is an authority role, orthogonal to kind. A reconciled ordinary
answer is still an Answer; an adjudicated screening assertion is still a ScreeningDecision,
with different authority ownership and no reviewer-vote contribution. An outcome computed
from several assertions is a Result, referencing its inputs and algorithm version.
Do not make every computed result an authored annotation: this would obscure attribution.
classDiagram
Annotation <|-- Answer
Annotation <|-- ScreeningDecision
Annotation "1" --> "many" AnnotationRevision
AnnotationRevision "1" --> "many" OwnedChildRevisionReference
ScreeningDecision --> ScreeningProfile
StageBinding --> ScreeningProfile
StageBinding --> QuestionSetVersion
ReviewSubmission --> AnnotationRevision : pins
ScreeningOutcome --> AnnotationRevision : derives from effective decisions
ReconciliationRecord --> AnnotationRevision : records authority and inputs
The specialisation arrows describe domain kinds, not a required C# class hierarchy. One domain vocabulary and lifecycle does not mean one unrestricted CRUD endpoint, identical natural keys, a single physical document, or identical allocation counts.
| Real choice | Evidence and tradeoff | Target recommendation |
|---|---|---|
Extend today's Annotation class |
It requires question/stage/annotator fields and typed subclasses; replacement/deletion/session filters have existing meanings. Making a new subclass does not change those semantics or supply decision history. Current model · Replacement | Do not use this unchanged class as the shared root. This is an evidence-based incompatibility, not a rejection of conceptual unification. |
| New common base with Answer and ScreeningDecision subclasses | Can enforce common immutable identity/revision behaviour and allow typed specialisation; requires discriminator migration, serializers and exhaustive readers to support both. | Viable if a prototype demonstrates no accidental generic mutation or old-client deserialization paths. Inheritance is an internal representation choice. |
| Shared Annotation envelope with discriminated kind payload and composed policies | Common IDs, revision store, queries, trees and exports; specialised validators/commands enforce screening invariants. Avoids pretending decisions have arbitrary ordinary question answers. | Preferred target implementation. Both kinds are first-class records in the unified annotation model, not parallel domain silos. |
| Separate Screening aggregate reusing form/answer helpers only | Lower transitional impact on existing ScreeningInfo writers; cannot by itself provide shared ownership/history/query semantics. |
Temporary compatibility adapter only. Remove the separate canonical decision representation at milestone M7; retain specialised policies, which are permanent. |
A future prototype chooses between the two viable target representations against identical conformance tests. Choosing composition does not postpone defining the common semantics: they are specified below and must hold whichever representation is used.
Why unify: concrete shared responsibilities and limits¶
The objective is to reduce duplicated infrastructure and make new scientific assertions possible without constructing another rigid screening-like subsystem. Sharing form rendering alone does not achieve this. The inspected code provides the following evidence; it does not prove that every similar line should be extracted into a generic service.
| Responsibility | Current evidence: duplicated or already shared | Common target responsibility | Specialisation that remains |
|---|---|---|---|
| Identity, scope and attribution | Screening and Annotation both repeat StudyId/ProjectId/StageId and a reviewer identity, but name the latter ScreenerId/AnnotatorId. Both already inherit Entity identity/time infrastructure. Models · Annotation |
One typed identity/context/actor contract and revision provenance; reuse existing base identity where appropriate | Natural keys differ by assertion semantics; stage is provenance for shared assertions, ownership for work |
| Definition and content | Annotation owns QuestionId, question text, AnswerType and typed subclasses; Screening owns a decision enum and relies on project threshold settings | Common versioned definition-reference and validated typed-content protocol | Ordinary question schema versus immutable profile decision definition; Include/Exclude is not an arbitrary string option |
| Child/conditional answers | Existing answer trees validate parent/question relationships and conditional parent answers; screening has no equivalent reason tree | Shared owned-tree validator, child revision references and condition evaluator; reasons use Answer infrastructure | Decision-root ownership and profile evidence requirements; non-owning visibility dependencies cannot use tree-deletion semantics |
| Write orchestration | TrySaveScreeningAsync and SubmitAnnotationSessionService.SubmitAsync independently manage operation/receipt setup, retries, fresh-state admission and save outcomes. Screening write · Session write |
One command-commit infrastructure for immutable revisions, receipts, CAS/transaction outcome and post-commit publication | Screening sufficiency versus session allocation, different write sets and safe retry boundaries; callbacks with non-repeatable side effects must not be blindly replayed |
| Membership/authorization | ReviewSubmissionService already shares GuardActiveMembership; controller policies and payload reconciliation checks differ |
Retain shared membership guard; consistent per-kind/per-role admission hook invoked by UI/API/import commands | Review, correction and adjudication authority differ; a common hook cannot mean identical grants |
| Lifecycle/history | Screening mutates its decision; sessions change status; answer replacement overwrites records. Neither supplies the proposed full immutable history | One head/revision/draft/submission protocol with explicit publication intent | Completing answers can promote candidates; draft reasons cannot promote a vote; combined intent atomically declares both effects |
| Reconciliation/aggregation | Screening computes a threshold result; annotations use reconciled flags/sessions and their own replacement rules | Common source-input vectors, immutable output/authority records, freshness and audit infrastructure | Vote counting, answer equivalence, manual adjudication and scientific scoring are distinct policies, never a universal “average answers” algorithm |
| Export/query | Both long row writers identify one reviewer and output creation time through the already shared DataFormatRowWriterBase; their data columns/selection paths differ. Answer row · Screening row |
Shared context/revision/attribution/blinding columns and authorized annotation query pipeline; common history pagination | Kind-specific content/outcome columns and counting grains; keep useful specialised export layouts over the shared source |
| Presence/capacity | ReviewSubmissionService already manipulates reservations for screening, and Study save graduates reservations into sessions |
Shared lease lifecycle, connection-holder and expiry infrastructure | Profile screening and stage annotation scope/targets; no generic “annotation count” used for both |
The duplication evidence concerns implementation responsibilities. Shared Mongo repositories, entity bases, submission service, export base class and AF2 adapters are already reusable assets, not systems to recreate under new names. Conversely, common immutable history and explicit context are missing target capabilities, not claimed existing duplications. This distinction keeps the abstraction grounded.
The common commit engine accepts a registered, typed kind policy, not arbitrary JSON or user-supplied code. That policy supplies definition resolution, natural-key construction, content and owned-tree validation, authorization/admission, permitted lifecycle effects, and result invalidation. The engine owns immutable revision storage, referential integrity, version checks, command idempotency, transaction publication, provenance and event delivery. Every kind participates in common contract tests. Result policies can use shared revision vectors and freshness without sharing their mathematical/scientific algorithm.
Prevent an unconstrained generic record: kind payloads have closed versioned schemas; unknown writer versions fail closed; ownership/context cannot be changed by ordinary edit; only declared lifecycle transitions are legal; registrations cannot bypass mandatory identity/authorization/transaction checks. Add a new kind only for genuinely different invariants. Most “decision-support annotations” are ordinary Answer annotations in a decision context and require no new kind. A new question or renderer is not automatically a new domain kind.
3.2 Identities and entities¶
| Concept | Identity and responsibility |
|---|---|
ScreeningProfile |
Project-owned ID, name, criteria text, agreement rule and optional shared minimum evidence requirements; screening eligibility question definitions/configuration belong to this profile (DP4); ordinary study-fact definitions remain project assets. Presentation bindings remain stage-specific. Immutable once used; clone to change scientific criteria/rule. Retain exact numeric legacy thresholds in the compatibility profile. Cosmetic labels can be separate metadata. |
| Stage/profile binding | Stage references a profile; owns ordinary annotation question set, review mode, selection/gating, completion policy and allocations. Several stages may reference the same profile. Binding version is checked at submit. |
ScreeningDecision |
Specialised Annotation kind; stable identity/natural key project + study + profile + reviewer. Points to one effective submitted revision, or none. Stage is provenance on revisions, not part of vote identity. |
DecisionRevision |
Screening payload of an immutable AnnotationRevision; immutable decision, author/actor, recorded time, source stage, criteria reference, answer-tree snapshot/references, predecessor, command ID and provenance (interactive, combined-completion, import, legacy-snapshot). One revision replaces one effective vote; revisions are never additional votes. |
| Decision draft | Saved unsubmitted reasons/eligibility answers, owned by reviewer + decision context. Incomplete content has no agreement/eligibility effect. It need not create an ordinary stage annotation session. |
ScreeningOutcome |
One computed result per study/profile: Pending, Conflict, Included, Excluded; retain decision count, tally and calculation version/revision inputs. Distinguish zero votes from insufficient votes in accompanying assessment progress. |
ScreeningAdjudication |
Reconciled-role ScreeningDecision annotation with separate immutable reconciler revisions and effective pointer, with source decision revisions. Overrides the operational result under explicit policy; never masquerades as another reviewer vote. |
AnnotationSession |
Stage-specific work/status and submission snapshots. References ordinary study-answer versions and, when combined, the exact decision revision used/created. Never determines vote cardinality. |
| Study answer / reconciled study answer | Answer-kind annotation; question identity + compatible question version + semantic context. Candidate adds reviewer identity; reconciled result instead has its own version authorship/source candidate references. Source stage is provenance. |
Use QuestionId, not matching labels, as identity. Context includes project/study,
subject/entity path (e.g. study vs a particular cohort/timepoint/repeated instance), and
answer purpose. Require an explicit compatibility mapping across changed question versions;
identical text is insufficient. For repeated answers, stable entity/root identity and parent
context prevent accidental merging.
Decision-specific answers additionally include profile + decision identity and are pinned to
a decision revision. Two profiles using a question with the same ID do not share a reason.
Ordinary study answers displayed conditionally on a decision stay in study context. A display
condition is a visibility/requirement rule, not ParentId ownership or cascade deletion.
Revoking reviewer access does not silently remove their historical effective vote; any
withdrawal needs an explicit audited policy/command. When a decision changes, retain hidden
study answers and old decision reasons; mark which
answers/revisions apply to the new submission. Do not feed hidden saved values to an export
as though they were current applicable answers without a scope/status column.
For shared ordinary answers, both stages address the same candidate identity for the same reviewer/context; their completed submissions pin versions. A later stage may edit the working answer without rewriting the earlier submission snapshot. Reconciliation groups compatible submitted candidate answers across stages, deduplicating reviewer + answer identity/version selection, so two sessions by the same reviewer do not become two independent candidates. Disagreeing versions from one reviewer require an explicit current candidate selection, not double weighting. A canonical reconciled study answer can be referenced by both stages; neither stage is falsely marked completed by this reuse.
3.3 State dimensions and eligibility¶
Do not overload a single Included/Excluded/null enum:
| Dimension | Examples | Meaning |
|---|---|---|
| Applicability/selection | Applicable, filtered out, not applicable, unknown | Whether this activity belongs in this stage/profile's target population, with rule/version/reason. Not a reviewer vote. |
| Personal assessment | Missing, reserved, draft/incomplete, submitted | What this reviewer has actually done. Reserved is transient; draft can persist. |
| Individual decision | Include, Exclude; effective revision may be absent | Only submitted decisions count. Missing/incomplete are not decisions. |
| Collective outcome | Pending, Conflict, Included, Excluded | Derived per profile from effective votes/adjudication. |
| Annotation work | Not started, incomplete, completed; candidate/reconciliation | Independent stage work and answer availability. |
Filtering before assessment creates no Exclude. For example, a diabetes stage limited to confirmed diabetes studies may have no assessment at all for a non-diabetes study; record its filter/applicability explanation if needed, not a fabricated scientific decision. Conversely, if a reviewer actually assesses that study against diabetes criteria and submits Exclude, that is a valid diabetes-profile vote. Unknown eligibility must not silently become NotApplicable. Absence from an assigned reviewer's pool may simply mean somebody else has capacity or an assignment; it is not even proof of profile inapplicability.
Stage eligibility rules name the profile and outcome explicitly, e.g. TA == Included.
They must say how Pending/Conflict/missing are handled (default: not admitted to a gate that
requires Included). Use separate rules for showing existing saved work. Losing fresh-work
eligibility preserves saved answers and their read/export access under authorization;
editing or completing stale work requires a clear current-policy decision. Profile outcomes
must not overwrite a global study-lifecycle status.
3.4 Combined review lifecycle¶
The optional stage setting means “Complete review records Include”, not “saving a form records Include.” The button must make that effect clear before use. Proposed command:
CompleteCombinedReview(project, stage, study, session,
expectedSessionRevision, expectedDecisionRevision, expectedBindingVersion,
commandId, ordinaryAnswers, decisionAnswers)
Server resolves the profile from the checked binding, author from authentication and current
permissions/allocation from authoritative state. It validates required applicable answers,
then atomically commits session completion/snapshot, any new Include revision, outcome and
reservation transitions. Return both persisted states and revisions. A frontend chain of
“save session, then send Include” is insufficient: partial failure would contradict the
button's meaning. The integration point is the server completion orchestrator adjacent to
SubmitAnnotationSessionService, not JoinStudyReview, draft persistence, or the host's
post-save navigation callback.
| Action/state | Proposed effect |
|---|---|
| Open, view, dirty form | Presence/appropriate reservations only; no vote or fake session |
| Save progress/autosave | Incomplete work preserved; no new/revised effective decision |
| Valid Complete in configured combined mode, no existing vote | Commit one Include if screening admission permits; validate both activities |
| Complete another stage with same profile and existing Include | Reference that effective decision; no additional vote, normally no identical decision revision |
| Complete with existing Exclude | Do not silently overwrite it. Require a deliberate correction to Include, displaying scope across shared stages, or an allowed annotation-only completion |
| Complete after profile capacity/policy changes | Recheck. If no vote is admissible, offer explicitly labelled annotation-only completion when allowed; otherwise retain draft and report the blocked activity. Never fake a successful Include |
| Explicit Exclude | Open decision-specific reasons; submit only when required reasons validate. Atomic Exclude + reasons; retain ordinary draft/session answers |
| Cancel exclusion reasons / failed submit | No new effective vote; existing decision remains; preserve the draft |
| Reopen completed annotations | Start/edit working draft; keep previous submitted decision until an explicit correction/withdrawal command |
| Retry / duplicate delivery | Same command returns same committed result, no duplicate revision or vote |
Review policy is distinct from agreement. Preserve the recorded Allow/Stop defaults in the eligibility-policy proposal, with a profile replacing the old project vote scope. Automatic Include must not bypass Stop. A combined completion does not require a separate Include button where admission permits it, but annotation-only contributors must still be able to complete allowed annotation work after screening is sufficient. Do not promise an automatic Include for every annotation completion irrespective of allocation/permissions.
3.5 Compatible allocation overlap and reservations¶
Keep two independent obligations: screening at study/profile/reviewer grain and annotation at study/stage/reviewer grain. A useful combined assignment is the intersection of those admissions, but the system also needs screening-only and annotation-only contributions. A shared profile's vote target is not multiplied by the number of linked stages.
Worked allocation: profile P needs two votes; stage A needs annotations from Alice/Bob, stage B from Alice/Carol. Alice's A completion and Bob's screening satisfy P. Alice's B completion references her P vote; Carol completes B without being forced to add a third vote under Stop. Bob need not have an annotation session in B. If Bob instead excludes in screening-only work, do not create an incomplete B session for him to “align” assignments. If the annotation form itself is the only assessment route, administrators must allocate enough eligible distinct screeners to reach P's threshold; show an unsatisfiable plan before activation, not a permanent queue that looks complete because nobody has available work.
Proposed typed claims:
- Screening claim: unique
(study, profile, reviewer), shared across linked-stage pages; source stage/connection references are metadata. Count distinct effective reviewers plus outstanding claims not already represented by a vote. Agreement uses only submitted votes. - Annotation claim:
(study, stage, reviewer); first real save graduates it to a saved session. No claim/session is manufactured for a screening-only contributor. - A page may hold either or both. Completing one activity releases/graduates only its claim. Shared page presence can coordinate timers, but leave/disconnect of one tab cannot free a claim still held in another tab or stage. Retain claim epoch/token checks for stale cleanup.
- Corrections use existing decision identity and explicit edit admission, not a new-vote slot. Snapshot/CAS checks serialize competing reviewers near the final slot; check source decision revisions again when agreement changes and more tie-break work becomes necessary.
Do not unconditionally reserve annotation capacity just to open a screening reason form.
Whether reason drafting consumes a screening claim is part of screening allocation; it has
no implication for annotation SessionCountTarget.
3.6 Target identity, ownership and revision contract¶
The common logical envelope is defined independently of Mongo/C# representation:
AnnotationHead
id, projectId, studyId, kind: Answer | ScreeningDecision
authorityRole: Candidate | Reconciled
definitionRef: QuestionIdentity | ScreeningProfileDecisionDefinition
contextId, subjectRef, ownerRef
headVersion, latestSavedRevisionRef?, effectiveSubmittedRevisionRef?
lifecycle: Active | Withdrawn | Archived
AnnotationRevision (immutable)
id, annotationId, sequence, predecessorRef?, definitionVersionRef
typedPayload, notes, ownedChildRevisionRefs[]
actorId, sourceStageId?, sourceSessionId?, submissionId?, commandId
recordedAt, observedAt?, provenance, sourceEvidenceRefs[]
ReviewWorkspace / saved session
id, projectId, studyId, stageId, reviewerId, activity, bindingVersion
draft references with base revisions, status, lastSavedSubmissionRef?
ReviewSubmission (immutable)
id, sessionId, state: SavedIncomplete | Completed
questionSetVersion, bindingVersion, annotationRevisionRefs[]
screeningEffect: None | Created | Reused | Corrected
screeningRevisionRef?, submittedBy, recordedAt, commandId
latestSavedRevisionRef is not automatically effectiveSubmittedRevisionRef. Saving a
reason draft may preserve an immutable saved revision without affecting the effective vote.
For ordinary answers, an incomplete saved snapshot is available as work but not implicitly
a submitted reconciliation candidate. A completed submission promotes compatible selected
answer revisions under an explicit candidate policy. Reopening creates a draft based on a
previous submission; it does not mutate that submission or retract its vote.
Do not force an unedited open into a persistent review-data session. A transient workspace and its claims can exist before a first saved session. A standalone screening decision can have command/submission provenance without a fabricated annotation session. A combined review's actual session pins its Answer and ScreeningDecision revisions together.
The natural keys differ because the assertions differ, while all use the same AnnotationId
and revision-reference type:
| Annotation role/kind | Unique semantic key (within project/study) | Owner |
|---|---|---|
| Candidate ordinary Answer | reviewer + question identity + subject/entity identity + ordinary context | Reviewer assertion identity; session is a contributor/reference, not exclusive owner |
| Candidate ScreeningDecision | reviewer + profile ID | Reviewer assertion identity; source stage never creates another vote |
| Decision-owned Answer | owning decision annotation ID + question identity + repeated child-instance identity + decision context | Decision tree; child authorship remains on revisions |
| Reconciled ordinary Answer | question identity + subject/entity identity + ordinary context + authority-policy scope | Project's reconciled assertion; each revision names its actual reconciler |
| Reconciled ScreeningDecision | profile ID + authority-policy scope | Project/profile adjudication; never the reconciler's candidate vote |
| Adjudication-owned Answer | adjudication annotation ID + question/instance identity + adjudication context | Adjudication tree; never a candidate reviewer's reason tree |
ContextId resolves an immutable structured record, not an opaque unvalidated caller string.
The record distinguishes ordinary context, decision context and deliberate independent
assessment context, including relevant entity/timepoint identities and interpretation.
ScreeningDecision context is fixed by the profile; an independent Answer context cannot
bypass its reviewer/study/profile vote key. Independent screening criteria require another
profile, not another stage or caller-invented context.
Question versions are pinned on revisions. A spelling/help-text update need not fork stable
answer identity; an incompatible semantic change requires a new definition identity or
explicitly versioned incompatible context. No automatic equivalence follows from equal text,
option ordinal or stage name. Identity/context compatibility is server-validated.
If two stages deliberately require independent answers to the same apparent question, provide an explicit independent-assessment context. Those answers must not be collapsed by ordinary shared-answer reconciliation. In particular, blind independent re-answering and one shared reviewer answer are incompatible promises. The versioning proposal favours sharing while the reconciliation proposal discusses cross-stage blinding. Offer explicit stage/context policy: share compatible answers, or create an independent observation. Never hide the current shared answer and then count the resulting re-answer as another independent reviewer vote. Versioning discussion · Reconciliation discussion
A reviewer can have drafts for the same shared answer in two stage workspaces. Each draft
pins a base revision. CAS detects concurrent edits; a stale save offers comparison/rebase,
not silent last-write-wins. Revert affects that workspace's draft only. It must not restore
an older value globally and erase a newer save from the other stage. Completed submissions
always read their pinned tree. This resolves a real ownership problem left open by a single
mutable pendingAnswer shared across stage sessions in the versioning discussion.
3.7 Questions, answer trees and non-owning conditions¶
A versioned question definition describes type, stable options, validation, repetition and permitted child-question relationships. A versioned stage question-set binding describes which ordinary questions to present and complete. A profile decision definition describes its Include/Exclude vocabulary and permitted evidence question sets. A definition may be reused by several bindings; a binding is not a new question identity. Ordinary question selection, layout and allocations remain stage-specific. A stage can also select decision-evidence questions; if a profile requires common evidence for a valid vote, every submitting stage must collect or reference that minimum set. A stage's extra evidence does not create a new profile or vote. Validate a binding that omits required profile evidence before enabling it. Adding or correcting evidence for an existing vote creates a new revision of the same decision, not a second vote. This separates shared decision validity from identical forms/assignments.
An owned answer tree is a revision-pinned graph with a single owning parent for each child within that tree version. ScreeningDecision is a legal root; Answer is a legal child and can own further Answer children. Repeated instances have stable IDs. Ownership forbids cycles, cross-project/study edges, a reason owned by two decisions, or a candidate child attached to a reconciled decision. Tree revisions include the child references needed to reconstruct exactly what was submitted. An edit creates a new revision/path to the changed child; old parents continue to reference old children. A DAG of reused immutable revisions is possible in storage, but it must not create two mutable owners for one decision-specific answer.
There are three different relationships:
| Relationship | Example | Effect |
|---|---|---|
| Owned child | Exclude → primary reason → population detail | Validated and submitted with that decision revision; history remains after correction |
| Shared reference | Stage A and B both submit the same ordinary population answer | Both pin compatible versions; no duplicate assertion or review vote |
| Visibility/requirement dependency | Show ordinary intervention question when FT is Included | Evaluates a named personal decision or collective outcome; no parent, ownership transfer or cascade deletion |
Conditions must specify OwnDecision(profile) versus CollectiveOutcome(profile) and whether
a local proposed decision can drive draft preview. A submitted form records the evaluated
context/version. Tentative Include can reveal the ordinary form before Complete commits it;
this is not a vote. Re-evaluate requiredness on the server using the command's proposed
state and checked authoritative outcome. Reject cyclic form dependencies at configuration
publication. Changing a decision may make saved answers hidden/inapplicable in the current
view, but does not destroy them or mutate past submissions.
Today's validator resolves parent annotations through their QuestionIds and concrete
Bool/String answer types. A ScreeningDecision root and non-owning cross-profile condition
therefore need an explicit definition/context resolver; simply putting a screening ID into
ParentId would not satisfy current validation. Preserve the reusable type and tree checks,
then separate ownership validation from visibility evaluation. Relationship validation
3.8 Individual, collective and reconciled authority¶
One effective candidate ScreeningDecision per reviewer/profile feeds agreement. Its revisions, reason children, session references and source stages do not add voters. Keep the raw candidate result and operational final result separately visible:
ScreeningOutcome(project, study, profile)
candidateInputRevision / effective decision revision references
candidateResult: Pending | Conflict | Included | Excluded
voteCounts, agreementRuleVersion, computedAt
adjudicationRevisionRef?, adjudicationFreshness
finalResult, finalSource: CandidateAgreement | Adjudication
A manual adjudication uses the same Annotation revision machinery but a reconciled authority role. It can confirm or override Include/Exclude and own its own reason tree. It records candidate inputs, author, rationale and which rule authorizes precedence. It neither edits reviewer decisions nor becomes an extra reviewer vote. Reconciliation of reasons does not silently change the decision; submit a declared compound adjudication when both change. Candidate agreement on Exclude and agreement on reasons are separate facts. That does not automatically authorise bypassing reconciliation or publishing an authoritative final outcome. Preserve the recorded FEAT-009 policy below instead of manufacturing agreement by selecting an arbitrary candidate reason.
Reconciliation with the earlier screening-annotations specification¶
The Screening Annotations specification is In-Review and contains recorded owner decisions; these are design inputs, not evidence of implemented behaviour. Its defaults are retained by this research:
- Decision disagreement requires resolution under the profile's configured independent-vote or adjudication route. An extra independent vote is not itself an adjudication.
- By default, screening annotations on candidates agreeing with the agreed decision require reconciliation. Agreement without screening annotations may bypass it.
- Bypass configuration belongs to the profile, not the stage. The default is no bypass for annotated decisions. Administrators can require exact agreement on the primary question, specified questions, or all questions; free-text answers are not implicitly exempt. An explicit reconcile-all policy can require oversight even for agreed Includes.
- Where bypass is permitted, derive authoritative answers only from candidates whose decisions match the agreed decision. Keep a branch only as far as all those candidates agree, truncating at disagreement. Preserve every original candidate answer; truncation affects the derived output only. Distinguish truncated output from missing source data.
- Manual reconciliation produces an attributed decision and its applicable answer tree together. It may confirm or override the candidates, without editing them or adding a vote.
For example, two Excludes with different primary reasons have candidate decision agreement but still require reconciliation under the default, or under primary-question bypass. Two Excludes with the same primary reason but differing child reasons may bypass only when the profile allows primary-question agreement; their derived tree stops at the child disagreement. Under the default no-bypass policy they still require reconciliation.
The earlier specification makes the Final Screening Outcome authoritative for downstream consumers. Therefore the previous sentence claiming that reason-conflicted Exclude could already be final for eligibility was too broad. Keep candidate result, reconciliation requirement, authoritative result availability and reason resolution separate. A dependency must explicitly name whether it uses candidate collective agreement or an authoritative final outcome; do not let consumers silently substitute one for the other. The exact effect of candidate Excluded while reason reconciliation is pending on the newer personal-plus-veto step policy remains an integration decision, not a reopened question about bypass defaults.
Following Chris's 25 September clarification, a submitted replacement of an input screening decision makes the old adjudication inapplicable to the new input vector, while retaining its historical validity and provenance. Re-evaluate current decisions and the profile's reconciliation/bypass rules. Do not automatically demand another adjudication: it is needed only if the new inputs still require it. Agreement alone is insufficient to bypass the annotation-reconciliation policy above. A draft correction changes no effective input.
For ordinary Answer reconciliation, the target is a versioned authoritative answer set per study/context, accumulated across stages. A ReconciliationRecord pins the input candidate revision vector and the output Answer revisions, including carried-forward answers. Distinct stage work items may contribute to that set; completing one does not complete every stage's reconciliation obligations. Preserve input blinding independently of administrator audit access. Accepting a candidate answer may reuse its immutable value, but still creates an attributed reconciliation act; it does not alter candidate ownership.
Define candidate selection explicitly: among eligible compatible completed submissions, use the latest accepted submitted revision for each reviewer/answer identity under a serialized effective-submission pointer. A historical reconciliation retains the old input vector. A later submission from the same reviewer replaces their current candidate rather than adding another. New candidates, changed questions or changed input versions mark a previous result stale. Ordinary study-answer authority is shared across compatible stages; decision-specific authority never crosses profile/decision ownership.
For ordinary answer authority, the recommended stale-authority policy is to retain the last result and full provenance for audit,
expose NeedsRevalidation, and do not present it as freshly reconciled. A gate requiring a
current authoritative result pauses new admissions until revalidated; already saved work
remains. Whether a project may explicitly continue using the prior result is a policy choice
in section 8, not a reason to omit freshness from the end-state model. Auto-promotion/bulk
approval uses the same record with a named policy or approving actor, never an invented
human reconciler. Disagreement metrics use independent reviewer observations, not versions
or repeated sessions, and declare their population and question/context compatibility.
3.9 Profile/stage binding, access and allocation policy¶
The target separates scientific definition from workflow:
- Profile owns eligibility criteria, agreement rule and any shared minimum decision-evidence requirements. Freeze these once used; clone for a new scientific assessment. A profile is project-owned and can have many stage bindings; it is not a stage's private screening round.
- A versioned stage binding names the profile to assess, ordinary question-set version, upstream outcome gates, display conditions, review activities, completion behaviour, Stop/Allow policy, allocation policy and visibility policy. Rebinding is prospective; outstanding forms submit against their checked binding or receive a stale-context conflict.
- Allocation plans name activity scope and target population version. Profile screening demand and stage annotation demand are independently satisfiable. A combined queue can prefer overlap, then expose remaining work for either activity. It must not force identical reviewer rosters or count existing same-profile voters as fresh screening demand.
The target access contract is evaluated per operation, on the server and at commit time:
| Operation | Required authority/context |
|---|---|
| Read personal draft/history | Active authorized project/stage access and owner, or explicit audited administrative access; retention is separate from continuing access |
| Submit/correct candidate screening | Active reviewer, authorized stage/profile binding and permitted activity; own decision identity; applicable allocation/admission; corrections checked separately from new-vote capacity |
| Submit ordinary answers | Stage review permission, compatible question/context, own candidate identity and annotation allocation; no implicit screening permission |
| Complete combined review | Both write authorities when a screening effect is requested; explicit annotation-only command if only that activity is permitted |
| Reconcile ordinary answers | Reconcile permission for the contributing scope; compatible input/output contexts and configured self-reconciliation policy |
| Adjudicate profile decision/reasons | Explicit profile adjudication permission through an authorized binding; Review permission alone cannot grant it |
| Read collective outcome | Permission to view the relevant study/profile outcome; does not automatically expose individual reasons or reviewers |
| View/export candidates, reasons or history | Separate data visibility and blinding policy; audit/export authorization, not merely membership of another stage sharing the profile |
| Publish bindings/profiles/allocations | Design/admin permission plus validity and satisfiability checks; immutable definitions cannot be edited around completed reviews |
A stage grant may be an entry point to a shared profile, but is not a blanket read grant to
all other stages' answers. Current user roles can adapt into this capability model; a new
permission enum is not required for every conceptual capability. API, imports, scheduled
jobs, SignalR subscriptions and exports must use the same authorization/context service.
Never bypass immutable ownership through a generic Answer patch, reconciled Boolean, or
administrator import route. Disable access without deleting retained scientific evidence.
A reservation is a lease for work, not an Annotation and not an allocation plan. The typed
claim semantics in section 3.5 are the target, with (scope, reviewer, study) uniqueness,
connection holders, expiry token and version. Presence reports engagement; a saved draft
reports retained work. Neither is an effective vote. Releasing a lease cannot erase the draft;
retaining the draft need not retain an unused screening lease forever. Configure separate
new-work caps and resumption rules rather than fabricating annotation incompletes.
3.10 Target API, storage and event boundaries¶
The destination has one logical Annotation repository and revision contract for both kinds, with typed command policies. Proposed API shapes are illustrative contracts, not existing routes or permission to implement them:
| Contract | Target semantics |
|---|---|
GET /projects/{p}/studies/{s}/annotations?context=... |
Common discriminated Answer/ScreeningDecision envelopes, filtered by authorized context/visibility; explicit current versus pinned revision reads |
GET .../annotations/{id}/revisions |
Immutable history and provenance, paginated; redacted as required; no reconstruction from mutable sessions |
PUT .../review-workspaces/{id}/draft |
Save drafts with base revision/CAS and binding version; no effective screening mutation |
POST .../review-submissions |
Explicit SaveIncomplete, CompleteAnnotations or CompleteCombined intent; atomically pins answer revisions and declared screening effects |
POST .../screening-profiles/{profile}/decisions |
Typed candidate submit/correct/withdraw intent with expected head and command ID; delegates to common annotation revision commit plus screening policy |
POST .../reconciliation-submissions |
Authorized scope, candidate input vector and declared ordinary-answer/adjudication outputs; cannot masquerade as reviewer votes |
GET .../screening-profiles/{profile}/outcomes |
Separate candidate/final result, counts, freshness and evaluation revision; no ambiguous global inclusion field |
| Profile/binding/assignment commands | Version-checked configuration and applicability changes; validate existing dependent work before publication |
| Export jobs | Explicit profile/context, current/history/submission-as-of, candidate/reconciled, applicability and blinding dimensions |
kind, author, owner and profile scope are not arbitrary mutable client fields. Shared API
representation is compatible with separate intention-revealing command routes; generic CRUD
is not allowed to bypass screening rules. Every successful mutation returns a durable command
receipt and relevant current revisions. Stale head/binding, invalid tree, forbidden scope and
capacity refusal have distinct typed responses; none is reported as a successful save.
Recommended physical target (collection names illustrative):
| Store / consistency boundary | Contents and rationale |
|---|---|
| Annotation heads | Common identity/context/role/kind plus bounded revision pointers. Unique kind-specific natural-key indexes. Logical query contract shared across both kinds. |
| Annotation revisions | Append-only common envelope and kind payload with child revision references; unique annotation/sequence and revision IDs. Separate rows avoid unbounded revision arrays in Study or head documents. |
| Workspaces and submissions | Draft state with base revisions; immutable saved/completed reference sets. Stage ownership and display order remain here. Paginate/chunk large immutable reference sets under an atomic published manifest. |
| Definitions and bindings | Immutable used profile configurations, question versions, stage question sets and binding versions, with mutable draft/admin metadata separate |
| Reconciliation records | Immutable input/output reference vectors and authority/freshness metadata; study/context or study/profile grain |
| Screening outcomes | Rebuildable per-study/profile projection with input version; efficient eligibility lookups; never the only copy of decisions |
| Allocations, leases and presence | Activity-scoped assignments and transient claims/connection holders, separate from scientific evidence |
| Command receipts/outbox | Idempotency, transaction outcome and durable post-commit invalidation intent |
| Study | Study metadata and optional bounded query summaries; no canonical annotation/session/history arrays or authoritative global inclusion |
A commit transaction includes changed annotation heads/revisions, published submission or adjudication, affected screening outcome, reservation transition, command receipt and outbox. First use also conditionally locks the exact profile definition as used. For very large forms, stage immutable revision objects as unreferenced data and atomically publish a bounded manifest/head update only after validation; staged objects are not observable submissions. Admit an explicit maximum until this path is proven, rather than acknowledge a half-published form. The concrete transaction-size strategy is an engineering validation gate, not a second user-facing semantic model.
Selection for screening must read a current outcome/head version from the same authoritative snapshot or reject/retry stale admission. Heavy dashboards can use projections with declared freshness and authoritative fallback. Outbox processing may redeliver; consumers deduplicate by event/operation and revision. SignalR carries authorized scope/revision invalidations; clients fetch fresh state, while reconnect/refetch repairs missed notifications. It is not the sole correctness mechanism or the authority that commits a decision. Do not promise exactly-once transport.
This intentionally differs from the older versioning proposal's embedded unbounded avs
and asvs: stable identities and immutable snapshots are retained, but histories are stored
separately to avoid document growth and whole-Study contention. The cost is multi-document
transactions and explicit manifests. Benchmarks and failure-injection tests must validate
that choice before adoption; an implementation can change physical partitioning while
preserving these logical contracts. Domain unification does not require a giant aggregate.
3.11 Target queries, statistics, reports and migration completion¶
Statistics are named measures over explicit populations, not a count of generic Annotation rows. The end state supplies:
| Measure | Grain and exclusions |
|---|---|
| Screening progress | Project/profile/study population version; distinct effective candidate reviewers and candidate/final outcomes, never history or child counts |
| Personal screening contribution | Reviewer/profile, unique studies; corrections/revisions separately reported as activity, not extra votes |
| Annotation work | Stage/reviewer/session saved/completed status and assignment denominator; shared answer reuse does not complete an unsubmitted stage |
| Shared answer coverage | Study/question/context, with missing/incomplete/submitted/authoritative/stale dimensions; distinct independent candidates |
| Reconciliation progress | Authority-policy scope and compatible input population; decision adjudication and ordinary answer authority counted separately |
| Allocation/capacity | Assigned demand, transient claims, saved work and remaining distinct reviewer demand at their own grains |
| Eligibility/reporting | Per-profile applicable/filtered/unknown/assessed populations; excluded by actual final decision; reasons distinguish unresolved/missing/not-required |
A stage showing a shared profile's progress labels it as shared and does not duplicate it in project totals. An outcome-gated ordinary answer is not counted as a reason simply because it is visible after Exclude. Reason reports use decision/adjudication-owned Answer revisions and specify candidate versus final authority. History exports contain one row per revision with source references; current exports contain one effective assertion per key. Missing and not-applicable are explicit status columns, not blanks overloaded as Exclude. Saved work remains exportable even after its stage loses fresh-work eligibility, subject to permissions.
The final migration state is one canonical annotation model, with a provenance-labelled legacy snapshot where history is unknowable. Legacy IDs are retained or mapped through a collision-checked immutable manifest. Existing answers, sessions, vote values and graph edges must remain recoverable; there is no invented historical Include or claimed historical version chain. Broken/ambiguous records remain preserved and visible to authorized remediation, and block their project's canonical cutover when needed.
Old API payloads can be supported temporarily by adapters; old storage is not indefinitely co-authoritative. After a project's cutover, all writers route through the canonical command service. Reader/serialization adapters may expose a representable legacy view but reject ambiguous multi-profile writes. Adoption finishes when all relevant consumers use explicit context/revision semantics, legacy writes and duplicated canonical stores are retired, and rollback uses a compatible canonical reader. Retaining an archived source snapshot for audit is not the same as continuing dual writes. Section 7 assigns each adapter a removal gate.
3.12 Extensibility worked example: a quality judgement¶
Consider a future QualityJudgement assertion for a study's bias domain with values
Low / Some concerns / High, required rationale/evidence, and a configured adjudication rule.
This is a hypothetical extension used to test the model, not a request to implement or
replace SyRF's existing risk-of-bias work. If all it needs is a categorical question and
ordinary answer reconciliation, model it as Answer and add no kind. A new kind is justified
only if it needs domain-specific rules, such as required evidence combinations or a mandated
judgement algorithm and authority lifecycle.
Alice drafts a judgement with a child Answer for supporting text. Draft save uses the common workspace/revision service and contributes no final assessment. On completion, the kind's policy validates the judgement/evidence and permission, while the common commit engine publishes the revision tree and submission. Bob completes an independent judgement under his own identity. A result policy can flag disagreement; an authorized adjudication writes a reconciled-role judgement with its input vector. Editing Alice's evidence creates a new revision and marks dependent authority stale through the common dependency protocol.
Reuse without new infrastructure:
- Natural key uses reviewer + study + bias-domain definition + assessment context, implemented by the kind policy within the shared identity contract. Attribution/history/CAS/receipts, tree ownership, pinned submissions and queries need no second implementation.
- Supporting rationale is an Answer child; ordinary study-design data merely displayed by this judgement remains a shared study Answer reference. Reusing questions does not merge differently owned evidence.
- Generic authorized history/export emits the common envelope and invokes a registered content-column projection. A new value renderer/policy may be needed, not a new history, import-provenance, export-job or reservation subsystem.
- The result policy may use domain rules rather than majority. It has no screening eligibility effect, no Include/Exclude conversion and no screening vote count. Any workflow gating on the judgement must be an explicit stage rule.
- An imported or automated suggestion retains its actual source and remains a proposal until the configured submission authority accepts it; the system does not invent a human author or count the suggestion as another independent reviewer.
The extension passes shared conformance tests plus its policy tests. If it requires copying screening's retry loop, revision store, reason-tree persistence or history export, the unified infrastructure has not met its goal. If it must adopt screening's agreement/eligibility rules merely to reuse that infrastructure, the abstraction is too restrictive. Both are acceptance failures, not reasons to hide the duplication behind shared rendering.
4. Worked domain examples¶
Title/abstract screening¶
Profile TA-v1: broad title/abstract criteria, automated dual. Stage TA supplies only screening.
Alice Include → one effective vote, Pending. Bob Exclude → two votes, Pending under the
existing automated rule. Carol Include → three votes, Included. Bob's later correction to
Include appends a revision but still leaves three reviewers, not four votes. Optional
exclusion reasons belong to Bob's Exclude revision and survive the correction. A downstream
full-text stage can require TA-v1 outcome == Included.
Full-text screening through annotation¶
Profile FT-v1 applies stricter full-text criteria, distinct from TA-v1. Stage FT shows ordinary study-design/data questions and any eligibility questions. Opening and saving half the form create no FT vote. Alice completes a valid configured form → Completed FT session plus one FT Include committed together. Bob selects Exclude → required “wrong population” reason and supporting detail are submitted as children of his FT decision. Bob's saved ordinary work survives; it need not be marked completed. FT disagreement follows FT's own rule. Alice's TA vote does not count towards FT, even if the same reviewer did both.
One profile shared across stages¶
Stages Methods and Outcomes both bind FT-v1. Alice's completion in Methods creates her FT Include. Her completion in Outcomes reuses that same effective vote. Each stage has its own questions, annotation target, allocation and completion status. A shared study-design question with identical identity/context reuses Alice's answer, with versions pinned by each submission. Reconciliation can compare Alice with Bob across stages for that study answer; it cannot count Alice twice. An FT exclusion reason remains attached to its decision revision and cannot overwrite a Methods study-level answer that happens to use the same question definition. Changing FT's criteria requires a new profile; relinking a stage does not reinterpret old submissions under the new criteria.
Diabetes sub-study¶
The parent review has broad metabolic-disease eligibility. A diabetes sub-study uses its own profile D-v1 with diabetes-specific population/intervention criteria. A study can be Included for the broad profile and Excluded for D-v1 without contradiction. An explicit D-v1 Exclude can have “non-diabetes population” as a decision child. If an upstream filter keeps a study out before anyone assesses it for D-v1, it has no D-v1 vote; report filtered/not assessed, not Excluded. A study-level “population diagnosis” answer may be shared across stages when identity/context match, but “meets this profile's population criterion” and reasons remain profile-contextual. A separate diabetes analysis question shown only after D-v1 Include is conditional ordinary annotation, not a child of the Include decision.
5. Migration assessment — additive and evidence-preserving¶
| Existing evidence | Safe mapping | Not inferable / prohibited inference |
|---|---|---|
Actual ScreeningInfo.Screenings record |
One legacy decision identity/current snapshot per study/project/reviewer in a dedicated compatibility profile; retain ID, current stage, value and stored timestamps as legacy provenance | Earlier values/stages, original criteria at each vote, correction time, reason or adjudication status |
| Project threshold/criteria currently stored | Snapshot as current legacy configuration; retain numeric custom settings | That every old decision used those criteria/thresholds at the time |
InclusionInfo[] |
Recalculate/compare threshold outcomes from the preserved current votes | Separate historical profiles or separate decisions for each array entry |
| Incomplete/completed session | Retain exact state, reconciliation flag, IDs and nullable timestamps; migrate only documented session semantics | Include, Exclude, full-text assessment or a vote for any named profile |
| Existing reasons/pseudo-screening question | Preserve as ordinary answers; produce an administrator-reviewed mapping preview if requested later | Convert based on a question label or completion status; invent a reviewer or historical Include |
| Annotation parent/child graph | Preserve IDs/edges and typed values; flag broken/ambiguous ownership without deleting | A decision relationship merely because an answer says “exclude” |
| Shared question answers | Preserve current identities/values and stage provenance; deterministic context mapping only where unambiguous | Prior versions overwritten by cross-stage saves; compatible context from text similarity alone |
| Reservation / idle / suspended claim | Drain or explicitly translate under a coordinated lease transition; never use as scientific evidence | A review, draft answer, completion or vote |
| Blank imports / missing sessions / filter absence | Missing/not assessed/unknown as supported by evidence | Exclude, Include, NotApplicable, or an abandoned review |
| Legacy screening via reconcile endpoint | Ordinary current legacy vote unless independent evidence identifies its origin | A privileged final adjudication from StageId or vote order alone |
A “Legacy project screening” profile is a compatibility scope, not a claim about historic scientific criteria. Do not attach legacy votes to the first user-created profile (which might be diabetes-specific), split them by current StageId, or copy them into every new profile. Administrator confirmation can assign a semantic label prospectively; it cannot recover overwritten evidence.
Recommended later migration protocol:
- Separately authorized, read-only inventory produces per-project counts/checksums, duplicate natural keys, invalid/missing reviewer IDs, dangling questions/edges, custom thresholds, missing times and oversized records. Use synthetic fixtures first; production inventory needs its own authorization. Quarantine ambiguities without modifying source.
- Deterministic mapping manifest: source study revision + legacy screening ID → target decision/snapshot ID + compatibility profile. Keep original records and unknown values. Idempotency prevents reruns from generating votes/revisions; do not silently deduplicate conflicting legacy records.
- Backfill shadow data with no serving cutover. Fence writes or capture/replay every writer (UI, imports, bulk update, reconciliation, threshold recalculation). If a study revision changes, re-read under the manifest protocol rather than overwrite newer work.
- Compare per-reviewer decisions, source IDs, study/profile outcomes, selected pools, permission-filtered API output, counts, exports, session/answer/edge checksums and null timestamp preservation. Count equality alone is insufficient.
- Switch one opted-in project only after all writer versions are compatible and validation succeeds. Keep a reversible legacy view only while exactly one profile remains representable. Record the cutover revision, validation evidence and operator action.
Rollback limit: multiple profiles or decision contexts cannot be losslessly represented
by today's single ScreeningInfo. After new-only writes, a flag-off must stop those writes
and retain a compatible read/export path; it must not flatten all profiles into old votes or
let an old whole-Study writer erase new fields. A pre-cutover shadow rollback can simply
restore legacy reads. A post-cutover rollback needs the previous profile-aware binary or
read-only containment plus verified export, not a destructive down-migration. Retain saved
work and history through both cases. Removal of legacy storage is a separate future approval.
6. Affected paths and contracts¶
| Area | Existing seam | Required future change |
|---|---|---|
| Domain/backend | ScreeningInfo, Screening, Study, ProjectAgreementThreshold, Stage, ReviewSubmissionService |
Profile resolution, effective decision identity, immutable revisions, pure agreement projection, explicit scope |
| Annotation ownership | Annotation, ExtractionInfo.AddAnnotations/DeleteSession, AnnotationSession, relationship validators |
Context-safe replacement/deletion; pin submitted versions; child ownership separate from display conditions |
| API/orchestration | ReviewController, SubmitAnnotationSessionService, submission DTOs/mappers, ScreeningController settings |
Atomic combined command; expected revisions/binding; server author; consistent route IDs; separate adjudication authorization; idempotency |
| Storage | StudyRepository mappings/CAS/capacity writes; ProjectRepository maps |
Additive profile/state fields, history store, writer floor; CSUUID compatibility; no new BSON subtype handed to old clients |
| Permissions | StagePermission, StageAuthorization, API authorization handlers |
Explicit profile read/submit/correct/adjudicate checks; stage grant must not reveal another stage's reasons; shared outcome visibility separately authorized |
| Allocation | StageWorkloadShareEligibility, StageReviewService, repository allocation reads |
Independent activity admission, overlap validation and unsatisfiable-target feedback; retain saved-work access |
| Reservations | SlotReservation, SessionTally, atomic claim filters, cleanup consumers |
Typed scope, profile-wide uniqueness, stage annotation claims, retry and expiry token semantics |
| Frontend | Stage review host/navigation/screening components; AF1/AF2; AnnotationFormDataSource/Persistence; stage actions/effects; session/screening entities/selectors |
Explicit Complete-and-Include mode; exclusion form; cross-stage effective vote; retain draft on failure/filter change; regenerate API types |
| Reconciliation | Stage reconcile host, AF2 candidate source/persistence; reconciliation pools | Shared ordinary-answer comparison; dedup candidates across stages; separate decision adjudication and answer reconciliation |
| Imports | ScreeningColumnHandler, StudyUpdateRecordProcessor, StudyReferenceFileParser; proposed annotation import |
Explicit profile/criteria, source provenance, batch idempotency; never infer missing decisions |
| Exports | Screening/annotation format options and row writers, export DTO/job/config, data-export UI | Profile/current-vs-history/applicability/context columns; stable long form; explicit unsupported legacy-format response for multiple profiles |
| Queries/statistics | Filters.cs, StudyStats.cs, review query services, project/stage/reviewer selectors |
Profile outcomes and distinct voters; stage work counts independent; denominators distinguish in-scope, assessed, incomplete, excluded and filtered |
| SignalR | NotificationHub, review connection/presence repositories, expiry consumers, web SignalR effects |
Scoped claim lifecycle and authorization; post-commit outcome invalidation to all authorized linked-stage views without leaking reasons |
| Flags | env-mapping.yaml, generated flags, API runtime flag provider, backend flag consumers |
Shared server-authoritative enablement, dependencies and project allowlist; preserve disabled semantics across UI and jobs |
The physical target and consistency boundaries are specified in section 3.10. Both kinds use the common Annotation head/revision contract. A separate screening implementation may be a legacy adapter while transitioning, but is not a second permanent canonical model. Existing Study summaries can support compatibility reads; they never redefine annotation ownership or replace the immutable revision store. Section 7 gives each adapter an explicit exit gate.
Materialised-statistics paths are an integration boundary only here. A future profile change must coordinate metric keys, configuration digests, compatibility/fallback and invalidation with that task's owner. No rebuild, flag activation, benchmark, implementation change or task resumption is part of this investigation. New profile statistics should have an authoritative query first; do not serve existing project-wide projections as though they already have profile grain.
7. Current-to-target delivery and adapter retirement¶
The end state in section 3 is the destination for this programme. Milestones progressively make it usable; they do not redefine temporary separation as the final architecture. Implementation, migrations and activation still require separate authorization. Flags below are proposed names, not existing configuration keys. Each semantic cutover is server-enforced across interactive and asynchronous writers, with explicit project/schema compatibility.
7.1 Current-to-target mapping¶
This mapping describes structural transition. Section 5 separately limits what historical data can truthfully populate the target.
| Current structure/behaviour | Target owner and preserved meaning | Transition / completion milestone |
|---|---|---|
Annotation typed subclasses with mutable embedded values |
Answer-kind head + typed immutable revisions; preserve original answer IDs via manifest | Read adapter initially; native simple forms M1, all admitted ordinary forms/context handling M4, legacy projects M7 |
Screening in ScreeningInfo.Screenings |
Candidate ScreeningDecision-kind head/revision under legacy compatibility profile; current value only where history absent | Legacy facade routes existing endpoint; canonical screening M2, stored legacy snapshots adopted M7 |
ProjectAgreementThreshold and tracked InclusionInfo[] |
Versioned profile rule and derived ScreeningOutcome keyed by profile; old numeric rule preserved | Compatibility calculator M2; explicit multi-profile queries M6; legacy authoritative fields retired M8 |
Annotation ParentId/Children and question conditional targets |
Revision-pinned owned edges plus separate visibility dependencies | Translate existing valid trees; decision roots M3; context-aware shared trees M4; ambiguous edges preserved for reviewed migration M7 |
StageId on answer/vote |
Revision provenance; independent versioned stage binding and stage work ownership | Read provenance throughout; remove StageId from shared assertion natural keys M2/M4; no historical profile inference |
AnnotationSession status and implicit answer set |
Workspace/saved session + immutable ReviewSubmissions with explicit revision membership and completion state | Legacy form adapter M1; combined submissions M3; all canonical sessions M4; legacy sessions adopted as labelled snapshots M7 |
| Reconciled Boolean/session | Reconciled authority role + authored revision + source-input ReconciliationRecord | Existing path remains compatible; explicit cross-stage authority/adjudication M5; do not fabricate missing historical input vectors at M7 |
| Whole-Study save plus separate screening/session retry paths | Common typed command commit engine with CAS, receipts, source-preserving adapters and policy hooks | Tracer M1; both kinds M2; interactive/import/admin writers converged M7 |
| Stage workload shares and one untyped reservation | Stage annotation assignments + profile screening assignments; typed claims and connection holders | Preserve legacy semantics until project switch; overlap and typed lifecycle M4; drain/translate legacy claims at M7 |
| Current long/wide exports and summary DTOs | Shared annotation/revision query envelope plus specialised reporting projections and explicit profile/context/status | History export M1/M2; multi-profile reports M6; legacy-format adapter retired or isolated as read-only formatter M8 |
| Project/stage/reviewer statistics | Explicit profile assertion/results and stage work populations, context-aware answer authority | Authoritative pilot measures M1/M2; full denominators/freshness M6; coordinate projection migration with separate owner before consumer retirement M8 |
| Hub presence and saved-work notifications | Scoped leases/presence plus durable post-commit revision invalidation | Preserve old connections while adapter active; typed cross-stage fan-out M4; all canonical subscriptions M7 |
7.2 Milestones and independently useful outcomes¶
| Milestone | Usable outcome and shared-model progress | Acceptance evidence required before release | Compatibility / rollback |
|---|---|---|---|
| M0 — target contract and engineering proof | Agree the complete model in section 3; prove both Answer and ScreeningDecision in a synthetic common-envelope/revision/command prototype. This milestone is design/test evidence, not a user-facing release. | A20–A23 plus BSON/transaction/CAS failure proof; demonstrate specialised policy cannot bypass common checks; bound large submission publication | No live cutover. Record schema/writer floor and rollback matrix before enabling any canonical writes |
| M1 — versioned ordinary annotation slice | For explicitly admitted new pilot stages with supported study questions, existing forms save/complete/reopen through the common engine; users can view/export unchanged prior completed answers after later edits | A3, A7, A10, A20–A22; historical snapshot and draft-conflict E2E; prove no duplicated history/receipt infrastructure | unifiedAnnotations default off + allowlist; unsupported forms stay wholly on legacy path before any canonical write. AF1/AF2 adapter translates the command, not canonical ownership. Rollback after new writes remains canonical-aware |
| M2 — screening as the second annotation kind | Explicit-profile Include/Exclude, corrections/history and current outcome/selection for opted-in projects; both kinds share revision/query/export/commit infrastructure | A1–A5, A10, A20, A23; compare shared contract tests for both kinds plus screening-specific thresholds; corrections preserve one effective vote | screeningProfiles depends on M1 infrastructure. Temporary legacy screening facade supports representable commands only. Profile-aware read-only containment if writers disabled |
| M3 — evidence trees and integrated full-text completion | Decision-owned inclusion/exclusion Answers, resumable reason drafts, non-owning conditional ordinary questions, and atomic Complete-and-Include; explicit Exclude preserves ordinary work | A6–A9, A12, A21, A23; distinguish open/save/complete; induced partial failure and retry; existing Exclude correction deliberate | screeningDecisionAnnotations and screeningOnReviewCompletion dependency gates. Ordinary compound submissions pin canonical revisions, never temporary copied trees as a second authority |
| M4 — shared stages, ordinary answers and compatible allocation | One profile across stages; independent annotation rosters; typed profile/stage claims; shared compatible answers with pinned versions and safe cross-stage drafts; all form shapes admitted to canonical path have parity | A4, A7, A11–A14, A21–A22; two-tab/stage races, saved-work access, explicit independent context, shared-reviewer deduplication and supported extraction/tree parity | Switch only complete project/activity scopes with coordinated lease handling; reject unsupported contexts before editing rather than split ownership between stores. Disable new claims without deleting saved work |
| M5 — unified authority and reconciliation | Shared ordinary answer authority across stages plus explicit profile decision/reason adjudication; source vectors, stale detection and preserved candidate history; reusable authority infrastructure with distinct result policies | A11, A24–A25; changed candidate inputs, differing reason agreement, blind views, concurrent reconciler CAS; no extra vote from adjudication | annotationAuthority gates new policy; existing snapshots stay readable. Retain previous authority with freshness marked; rollback cannot rewrite candidate assertions or silently choose another final result |
| M6 — multi-profile workflows and complete reporting | TA/FT/diabetes criteria with explicit gates/applicability; profile progress, stage work, reasons and as-of exports use canonical queries; all linked views update coherently | A10, A15–A16, A26; worked examples as E2E fixtures, denominator reconciliation, export round trip, authorized SignalR/refetch recovery | Explicit profile/context required; no global inclusion fallback. Projection cutovers separately coordinated; authoritative query available when a projection is incompatible or stale |
| M7 — reviewed legacy adoption and writer convergence | Authorized projects adopt provenance-labelled snapshots without invented history; all their UI/import/bulk/admin writers use the common command engine; no parallel canonical Screening/Annotation store | A17–A19, A27; deterministic manifest, idempotent catch-up, every writer enumerated, source/target parity and rollback rehearsal | Separate inventory/migration approval. Per-project cutover after writer floor; archive originals. Retire translation writers after their gate below; unresolved legacy projects remain explicitly legacy, not silently partly migrated |
| M8 — completion of convergence | Consumers use canonical context/revision/result APIs; remove obsolete whole-Study annotation/screening authority, dual-write paths and migration-only flags; performance and supported large-form limits proven | A28; consumer inventory empty for legacy authority, export/audit parity, compatible restore rehearsal, scale acceptance for agreed workloads | Separate retirement approval and retention policy; immutable source archives retained as required. Old binaries that cannot read canonical data are no longer supported rollback targets |
Dependencies are M0 → M1 → M2 → M3 → M4 → M5 → M6 → M7 → M8 for adoption of the complete model. Some engineering can be scheduled independently later, but no release may skip a semantic prerequisite. M1 is a small real user slice, not a demand to rebuild every form before screening; M2 already gives screening a canonical kind in the shared domain. The constrained pilot is an activation boundary only. It does not change the target schema.
The quality-judgement extension in section 3.12 is a conformance exercise in M0/M2, not an extra production feature milestone. Bulk reconciliation, richer authoring UX and additional scientific kinds can follow M5 on the defined model; they are optional enhancements. Basic reconciliation authority, context-safe sharing, explicit reporting and adapter retirement are required destination capabilities and are not an unbounded “later” bucket.
M0 must establish the correctness/writer compatibility needed by the first release. Later milestones expand acceptance against the same model. Start with synthetic integration/E2E, then separately authorized nonproduction pilots. Production activation and migration are independent decisions; no milestone's passage implies either is authorized now.
7.3 Temporary adapters and explicit exit gates¶
| Adapter | Purpose while present | Removal milestone and evidence |
|---|---|---|
| Existing form/session DTO → canonical submission adapter | Allows AF1/AF2 supported forms to use shared revisions without rewriting all rendering at once | M4 for admitted canonical forms: frontend sends explicit revision/context/binding intents; equivalent old payloads remain only in versioned API compatibility facade until M8 |
Legacy ScreeningInfo command facade |
Routes one-profile legacy routes without changing existing clients' meanings; no second canonical decision authority | M7 per adopted project: all interactive/import/admin writers call shared engine; legacy storage read-only. M8 removes write facade globally after legacy clients/projects are accounted for |
Current Annotation/ExtractionInfo tree translator |
Converts valid old typed answers/edges to shared context and revision references | M7: manifests and full form parity complete; unresolved graphs block affected cutover. Delete old replacement/deletion writer for adopted projects |
| Separate interim decision-head/revision persistence, if a spike uses it | Temporary bridge only; must expose canonical IDs/revision protocol and one writer authority | M2 for new canonical screening: use common logical repository and revision protocol. Any legacy bridge left behind retires at M7. Separate typed policies remain permanently; duplicated canonical histories do not |
| Whole-Study screening summary/threshold query adapter | Keeps legacy selectors usable while canonical outcomes are introduced | M6 for native consumers; M8 for all compatibility readers after projection/report owner confirms parity. Optional rebuilt summaries may remain caches, never canonical votes |
| Untyped stage reservation bridge | Keeps older connections from conflicting with typed leases during scope switch | M4 canonical scopes, M7 adopted legacy scopes: drain or translate with token/version mapping; no old active claim writers before removing bridge |
| Legacy reconciliation Boolean/session view | Exposes current saved work without inventing source vectors or automatic authority | M5 new canonical work, M7 legacy snapshots: explicit LegacyAuthorityUnknown provenance where necessary; retire writable Boolean path |
| Dual-read/backfill routing and migration flags | Permits verified per-project adoption and catch-up | M8, only after project/consumer inventory, manifest parity, compatible rollback evidence and retirement approval; keep audit manifests/source archives |
| Legacy CSV formatter | Supports known external workflows during transition | M8 removes it as a source/query adapter. It may remain a documented read-only format over canonical queries where lossless; ambiguous multi-profile layouts fail explicitly |
A shared policy interface, specialised decision command, profile outcome projection and activity-specific lease key are permanent domain specialisations, not adapters slated for removal. An old class renamed behind a facade, two competing effective pointers, or a renderer shared while storage/history stay duplicated does not satisfy M7/M8. If evidence requires a physical store split for performance, keep one logical ownership/revision contract and record the partitioning decision; do not restore two domain models accidentally.
7.4 Acceptance-test matrix¶
| ID | Scenario and required assertion |
|---|---|
| A1 | Repeated command and concurrent duplicate submit leave one head, one effective vote and one command result; correction appends history without increasing voter count. |
| A2 | Single/manual dual/automated dual/custom thresholds: missing, insufficient, conflict, included/excluded and corrections match characterised legacy rules in compatibility mode. |
| A3 | Open, dirty, idle, disconnect and draft save never create a vote or alter an existing effective decision. |
| A4 | Same reviewer submits in two stages using one profile: one vote; different profiles: independent votes. Concurrent stages cannot lose revisions or double-count. |
| A5 | Inactive/nonmember/wrong-stage/wrong-profile/forged author or session ID requests fail before mutation. Review-only permission cannot adjudicate or submit reconciliation by toggling a body flag. |
| A6 | Missing required reason blocks effective Exclude; cancel/error retains draft and previous vote; valid Exclude commits its child tree and outcome together. |
| A7 | Changing a decision preserves old reasons and ordinary saved answers; conditional study questions remain study-owned. Same question ID with different profile/entity context never collides. |
| A8 | Valid Complete-and-Include commits both states; injected failure before commit commits neither; retry after unknown commit returns the original result. Navigation waits for acknowledgement. |
| A9 | Incomplete save or reopen creates no Include. Existing Exclude is never silently changed by annotation completion. Stop/Allow admission is rechecked under concurrent completion. |
| A10 | Export current vs history is explicit; blank/missing/incomplete/not-applicable/excluded stay distinct; no reason or reviewer identity leaks through blinded/unauthorized views. |
| A11 | Shared question/context across stages reuses reviewer answer identity, pins submitted versions, preserves exclusive child branches, and reconciles without counting the reviewer twice. |
| A12 | Screening-only and annotation-only assigned reviewers can each finish their authorized activity; no fake incomplete annotation session; claimed work survives allocation changes as specified. |
| A13 | Two stages/tabs share a screening claim; leaving one does not release the other. Screening completion releases only screening claim; first annotation save graduates only annotation claim. Stale expiry cannot remove a newer claim. |
| A14 | Distinct reviewer capacity, final-slot races, vote corrections reopening disagreement, and unsatisfiable assignments produce correct remaining-work queues and counts. No available work for one reviewer does not mark a stage complete. |
| A15 | TA Included + FT Excluded + diabetes missing/filtered produces independent outcomes and correct denominators; no automatic downstream Exclude on filtering. |
| A16 | Profile/binding change while a form is open returns typed stale-context conflict and preserves draft; no vote is silently reinterpreted. |
| A17 | Legacy annotations only, including completed sessions and pseudo-screening reasons, produce zero fabricated screening votes. Unknown timestamps remain unknown. |
| A18 | Backfill rerun, interrupted batch and concurrent import/correction are idempotent; source IDs/values/history/checksums match; ambiguous duplicate records stay preserved and blocked for review. |
| A19 | Mixed-version and flag-disable rehearsal demonstrates old writers cannot drop fields; multi-profile rollback never flattens votes or deletes saved work; source/query/export consistency survives restart. |
| A20 | Answer and ScreeningDecision pass the same identity/context/attribution/revision/CAS/idempotency/history-query contract suite through one logical repository and commit engine; kind-specific keys and payload validators remain enforced. No copied retry/history implementation. |
| A21 | Owned reason descendants pin exactly the submitted versions; a non-owning visibility dependency cannot move/delete ordinary saved answers. Cross-owner edges, cycles, wrong study/profile and stale definition contexts fail before publication. |
| A22 | Two stage drafts based on one shared answer conflict/rebase safely; reverting one never changes the other's submitted answer. Completed snapshots remain fixed. Explicit independent contexts stay separate and are never counted as an additional screening reviewer. |
| A23 | A synthetic quality-judgement kind reuses common storage, command receipts, revision history, tree handling and export without acquiring screening agreement/eligibility effects. A plain decision-support question needs only Answer, not a new subsystem or arbitrary kind. |
| A24 | Reconciliation pins one compatible submitted candidate per reviewer/answer context; accepted candidate reuse still creates an attributed act; shared gold-standard answers carry forward across stages without falsely completing stage work. |
| A25 | Adjudication overrides only under explicit authority policy, never adds a vote. A submitted decision replacement retires its old adjudication from current applicability and re-evaluates profile rules; drafts do not. Candidate Exclude agreement with reason disagreement preserves separate states and respects FEAT-009 reconciliation defaults. Primary-question bypass truncates disagreeing child branches without deleting candidates; no-bypass still requires reconciliation. |
| A26 | Profile totals deduplicate shared-stage display, session totals retain stage work, and missing/incomplete/filtered/not-applicable/excluded denominators reconcile across query, export and UI. Dropped/duplicate SignalR events recover via revision refetch without leaking reasons. |
| A27 | Writer inventory covers ordinary saves, screening, reconciliation, imports, bulk updates and administrative changes. After cutover none can mutate legacy canonical arrays or create a second history; source archive and manifest remain recoverable. |
| A28 | All temporary adapters meet their documented exit gate; legacy consumer inventory is empty or explicitly limited to lossless read-only formatting. Compatible canonical restore preserves history, ownership, snapshots, outcomes and saved work without old writer binaries. |
8. Genuinely unresolved user decisions¶
These do not block the recommendation or research delivery. They must be settled before the dependent implementation phase; none authorizes runtime work now.
- Scope of combined completion: which stages opt into Complete-and-Include, and which eligibility answers are decision-owned versus ordinary shared study answers? Recommendation: explicit stage opt-in and a visible completion label; no inference from existing forms.
- Correction after shared use: when an effective vote changes after several stages used it, should previously completed stage work require re-review, or simply retain its pinned decision and show a stale-eligibility warning? Recommendation: preserve submissions, mark affected work, never silently reopen/delete it. Define when explicit vote withdrawal is allowed.
- Default authority policy: the target supports separate attributed adjudication of decisions and/or reasons, and detects stale input revisions. Decide which projects permit which overrides, self-reconciliation and use of stale authority for new admissions. Recommendation: explicit grants, no self-reconciliation by default, and current authority required by authoritative gates. Existing FEAT-009 discussions are not final approval of these defaults; this does not leave the authority model itself undefined.
- Reason reporting: one primary exclusion reason versus multiple counted reasons, and treatment of missing/unretrieved full text, need a reporting policy. Do not infer PRISMA categories from arbitrary ordinary answers or duplicate studies as multiple exclusions.
- Legacy semantic attribution: an administrator must identify what existing screening actually represented before giving its compatibility profile a scientific label. A project with annotation-as-screening needs an explicit answer/value/reviewer mapping and review, not an automatic conversion based on “Completed.”
Not reopened as user questions: one vote per reviewer/study/profile, preservation of saved work/history, stage-specific allocations, no votes from drafts, no Exclude from filtering, or the recorded eligibility-policy Allow/Stop defaults. Physical schema/index details, CAS/transaction mechanics and typed API errors are engineering decisions to validate in M0, not reasons to delay this design behind a broad product interview.
Source and test references¶
All source references below are pinned to the inspected commit. They identify implementation seams and evidence, not guarantees about deployment flags or production data.
Latest confirmed direction and concrete approval proposals — 3 October 2026¶
This section supersedes earlier deferred/open wording for P9–P14 where stated; original source prototype and historical discussion are retained, not rewritten as implemented behavior.
- LC1 / P11: automatic completion requires no unresolved applicable work, including drafts and outstanding corrections. Alert project admin and obtain confirmation before admitting a change that would reopen a Completed stage. Commit approved change then auto transition in automatic mode; manual mode requires explicit reopen/switch-off. Preserve new-study auto reopening under this gate, current Complete counting until actual incomplete-version action, autosave/draft distinction and history. Precise approval/concurrency/draft routing is proposed, not a newly approved permission or spontaneous reopening.
- UA1 / P14: required applicable questions cannot be omitted in completed candidates; optional unanswered questions can be decided by the reconciler. Blank is not N/A. Configurable all-applicable-gold completeness was suggested; project/form scope/default and statistical missing/Unknown comparisons/denominators remain approval proposals. AG3 N/A/version and EX2 available-history decisions stay settled; Complete anyway never waives answer validation.
- PM2 / P12: project owner can assign permission administration to a membership group; authorized members administer/delegate within approved scope. This deliberately extends currently owner-only AssignPermissions. Ownership transfer remains owner-only. Proposed owner-reserved delegation-envelope administration/non-recursive delegation boundaries are explicit recommendations. Possessing a grant never means administering it. Group template names/bundles are implementer discretion after complete action inventory, not owner blockers.
- ODIR1 / P10: one versioned outcome-measure direction across cohorts in a paper/population; no context override. Genuinely different meanings require separate measures. Earlier open override proposal is superseded; retained conflicting legacy values require reviewed mapping.
- MIG1 / P9: drafting outcome migration/adoption plan is now authorized, superseding deferred planning status. Execution, activation and live migration remain unauthorized.
- IP1: concrete implementation planning authorized, not runtime code. Major product choices are covered; review remaining exact proposals before implementation instead of reopening them.
Reviewable standalone documents:
- Permission matrix proposal
- RBAC primary-source research
- Outcome-data migration plan
- Lifecycle and gold-answer settings proposal
- Implementation sequence
All are planning deliverables. Approval choices are marked; no default/grant, data conversion, statistical formula, production lifecycle behavior or source-prototype update is claimed.
DP6 — current cross-stage correction (supersedes DP1 cross-stage extension)¶
Chris, 3 October 2026, during PRISMA integration: personal Include with collective Exclude veto applies to steps within the same stage. Between stages, route availability should be configurable. Proposed choices are Collective Include required versus own Include sufficient; collective Exclude veto, permissions/allocation and independent gating remain. DP7 confirms default Collective Include required, with advanced own-Include option; collective Exclude veto retained. Record this as a change of direction, not a denial of earlier DP1 confirmation. Older blanket personal-Include cross-stage language in historical sections is superseded. Keep within-stage personal work, cross-stage routing and PRISMA collective report authority separate. Cross-stage default is settled under DP7. Review strict within-stage proposal and PRISMA amendments before implementation approval.