Skip to content

Temporary planning review record. The report below is reproduced verbatim as returned by the independent read-only reviewer (Plan agent, Fable model, launched 3 October 2026 about 14:35 BST). Only this front matter and note were added. Line numbers refer to the package as it stood when the reviewer read it. Resolutions are in the round-2 resolution matrix.

Review VA — versioning model, conceptual soundness

Reviewer: independent adversarial reviewer (versioning dimension), read-only, 3 October 2026. Nothing was created, edited or moved.

Path legend (absolute roots): - PKG = /home/chris/workspace/syrf/pr/pr3617.research-screening-as-specialised-annotation-gxgahs/docs/planning/integrated-review-plan-2026-10/ - PLN = /home/chris/workspace/syrf/pr/pr3617.research-screening-as-specialised-annotation-gxgahs/docs/planning/ (ledger = PLN/review-form-owner-decisions-2026-10-02.md) - MAIN = /home/chris/workspace/syrf/main/ at 0f5c61073 (checked today; the cited files are unchanged from the package's 78c6d097d baseline) - CORE = MAIN/src/libs/project-management/SyRF.ProjectManagement.Core/Model/ - V10 = /home/chris/.codex/visualizations/2026/09/23/01a0cbec-0c32-7103-ab5a-bfc05665deb7/syrf-v10-review-2026-10-02/source/design_handoff_syrf_v10/

1. Verdict

The versioning model is the right shape (stable identities, append-only revisions and versions, pins on every submission, a per-project commit sequence for as-of reads) and it restates the owner decisions faithfully, but it is not yet internally consistent and cannot be frozen at F1 as written. Six owner decisions (SF3, SF4/RE3, FV3, AG3, RE4, RE5) and three contracts (C2, C4, C9) all hang on a "compatibility" relation between question versions that the package never defines: it does not say who declares it, when, whether it is symmetric or transitive, whether it is a property of a question version (as domain-model.md says) or of a form publication (as v10 and FV4 imply), or how it differs from "this particular answer is still valid under that version". On top of that, the C2 head key has no version component and the head has one linear "current" pointer, yet per-form publication policies (doNothing in form F, requireReanswer in form G) explicitly keep two live forms on incompatible versions of one shared question; the model then branches and produces false "outdated" flags and an ambiguous "current" answer. The C5 "policy transition creates incomplete versions" contradicts SL3 ("latest explicit Save or Complete is current"). Several version containers are over-stuffed (form target, allocation and batch settings, DP5 toggle and routes inside immutable requirement versions), so routine operational changes would trigger FV2/Q-26 impact flows, and the "snapshot per code revision" rule for system questions makes a CAMARADES wording fix a forced per-project publication. The reconciliation task key "study × form × version-compatibility class" (a first-round resolution of B-28) is inadequate because compatibility is a per-question property, and it contradicts RE4's one task per study and form.

The five most important changes: (1) write one definition of compatibility (system-suggested, admin-confirmed at question-version creation, immutable once answered under, transitive into classes) plus a separate per-answer validity check, and put the class into the C2 head key before F1; (2) make publication a recorded policy whose effects are derived at read time, never a writer of session versions or answer revisions (option mapping under Q-34 being the one audited exception), which also makes FV4 and late Saves trivial and resolves the SL3 contradiction; (3) split every "requirement version" (question content, form question pins, profile criteria, stage bindings and steps) from "operational settings" (target, compare settings, gold completeness, routes, DP5, allocation, batches, expiry) that get audit history but no impact flow; (4) key the task by study × form only and move compatibility to per-question "held" state, gold re-reconciliation flags and agreement flags; (5) give options a stable identity and give sessions a full pin map, because option renames and delta-only pins silently re-mean stored answers and make previous-version exports ambiguous.

2. Findings

ID Severity Location Finding Evidence Recommended change
VA-01 Blocker PKG/contracts.md C2 l.143, C4 l.198, l.212–216; PKG/domain-model.md l.75; PKG/open-questions-and-assumptions.md E1 l.103, Q-34 l.63; PKG/acceptance-criteria.md AC-R2c-02 l.190, AC-R2d-01 l.203, AC-R5c-01 l.323 "Compatibility" is load-bearing for six decisions and three contracts but is never defined. The package uses it in four incompatible senses: (a) an immutable property of a question version ("compatibility with the previous version" in the QuestionDefinitionVersion, domain-model l.75; C4 l.198); (b) a per-form publication choice (C4's per-question requireReanswer/autoUpdate/doNothing, the v10 "Compatible / Not compatible" switch at form publish); © something revisable later (FV4; v10 RD12 "Mark compatible"); (d) a predicate on answers ("compatible answers are shared", AC-R2c-02 "carries compatible answers forward"). Nothing says who decides, when, whether it is symmetric (it is not: "options were only added" means a v1 answer is valid under v2 but a v2 answer selecting the new option is not valid under v1), whether it is transitive across v1→v2→v3 (RE4 needs an equivalence relation to form a "class"; FEAT-001 defines transitivity only for breaking), or how declared version compatibility differs from a particular answer's validity under a version (FEAT-003 separates these explicitly as "contextual breaking"; the handoff recovered that rule). C2 freezes at F1 "against question-version compatibility metadata (C4)" that does not exist. Ledger SF3 l.38 ("Context/question compatibility remains mandatory"), FV3 l.50 ("Transition policy is distinct from question-level answer compatibility"), AG3 l.803–814, RE4 l.655–669, RE5 l.671–677, SF4 l.414–419. MAIN/docs/features/annotation-versioning/README.md l.208 (BreakingChange set at version creation), l.362–368 (transitivity). MAIN/docs/features/question-management/README.md l.232–240 ("The BreakingChange flag is the admin's general classification; the reconstruction algorithm performs actual break detection per-session"), l.251–260. PLN/review-steps-prototype-handoff.md l.311–314 ("Per-submission validation detects contextual breaks even if the designer labelled a change non-breaking"). V10/RECONCILIATION.md l.163–170 (switch at form publish), V10/DECISIONS.md l.312–318 (RD12). PLN/screening-specialised-annotation-research.md l.690–693 ("Identity/context compatibility is server-validated"). Add a "Compatibility" section to C4 (frozen at F1, not F2) with: (1) compatibleWithPrevious(Q, n) decided when version n is committed in the designer: system default computed from the diff (data type, multiplicity, removed/retired option, meaning-changed option → incompatible; added option, wording, help, validator tightening → compatible), admin may tighten freely and may loosen only where no structural change exists and a Q-34 mapping is supplied; (2) the value is immutable once any revision pins version n; (3) class(n) = class(n−1) ∪ {n} if compatible else {n}; classId = lowest version in the class, so compatibility is an equivalence relation and non-adjacent versions are compatible iff same class; (4) a separate computed answer validity check (value in the target version's options by option ID, data type, validators) run by the E23 evaluator; a compatible class with an invalid value yields Needs updating for that answer regardless of policy. State explicitly how each decision uses it: SF3 shares only within a class; AG3 compares only within a class and flags differing versions; FV3 qualification = class match and validity per answer; RE4 holds per question when candidates' pinned revisions span classes; RE5 prefill only within a class; gold answers whose class differs from the form's current pin are flagged for re-reconciliation. Add fixtures for each.
VA-02 Blocker PKG/contracts.md C2 l.138–149; PKG/domain-model.md l.37–39 (principle 3), l.89, l.218–222; PKG/contracts.md C5 l.258–262 (Fix); PKG/acceptance-criteria.md AC-R2d-03, AC-R2d-04 A shared head with no version in its key and one linear "current" pointer cannot represent what per-form publication policies create. Publication is per form over "every stage using the form" (C4 l.212–216), so after Q v2 is published in form F (requireReanswer) while form G keeps Q v1 (doNothing), two live forms pin incompatible versions of one shared head. Then: the reviewer re-answers in F → new revision (v2) becomes the head's current → G's session is flagged "contains outdated annotations" (C2 l.147–149) although nothing SF5-like happened and G cannot even render a v2-only value; the reviewer later edits in G (v1) → a newer v1 revision becomes current → F's session is now "outdated". "Current" alternates between incompatible branches, SF5/SF6 flags fire falsely, and Fix (C5 l.260–261, AC-R2d-04) has no defined value to show. The same applies to profile-owned heads under Q-26 (ownerScope = profileId, no version). Review B-26 fixed author and population in the key; it did not consider version. The key freezes at F1. C2 key: {projectId, studyId, author or authority scope, ownerScope, questionId, entityPath[], populationId} (l.138–141), "Compatibility is a separate check" (l.143). "one head per author or authority scope per context; the current pointer changes by compare-and-set" (domain-model l.89). Per-form doNothing is explicit in the recovered baseline (ledger l.126) and FV2 (ledger l.49). Research A22 (PLN/screening-specialised-annotation-research.md l.1267) requires explicit independent contexts to stay separate. Add the question's classId (VA-01) to the C2 head key: one head per context and compatibility class. A revision under an incompatible version creates a new head (same questionId, new class) whose lineage is shown by SF5 ("all ancestors and prior own answers", read-only across classes). "Current", "outdated" (SF5/SF6) and Fix are then well defined within a class; AG3 "incompatible versions not compared" and SF3 "compatible answers shared" become structural. Alternative if the key must stay: one current pointer per class on the head; this is strictly more complex. Add conformance tests: two forms on incompatible versions of one question never flag each other; a v2-only option never appears in a v1 session; Fix shows the in-class current revision. Settle before F1.
VA-03 Major PKG/contracts.md C5 l.258–262 ("policy transition (a recorded publication choice that creates incomplete versions; SF6)"), C4 l.217–223 ("apply transitions idempotently in batches, or evaluate the recorded policy lazily on read"); PKG/integrated-plan.md l.125–126 (invariant 2), R2c l.469–473; PKG/acceptance-criteria.md AC-R2c-02, AC-R2c-05, AC-R2c-06, AC-R2d-06 Publication effects are modelled two incompatible ways, and one of them contradicts SL3. SL3 says the latest explicit Save or Complete is current; a "policy transition that creates incomplete versions" is neither, so after publication the session's current version would be one no reviewer made (actor = the publish operation), FV4 revision would then have to resurrect or supersede it, and a 10,000-session publication must write 10,000 versions (AC-R2c-06's fence target depends on it). autoUpdate is likewise read as writing carried-forward revisions (recovered from FEAT-001's impact-update AVs), which makes the publish operation an author of evidence on a shared head and feeds VA-02. SF6 only says "if … a recorded admin update treatment creates a new incomplete session version", it does not require one. The ledger's publish-pause guidance forbids rebasing; the "late Save swept into the policy" rule (C4 l.221–223) is unspecified when the session is not in the frozen manifest. Ledger SL3 l.44; SF6 l.527–540 (conditional wording, "triggers and mechanisms follow the particular recorded action/policy"); FV4 l.374–385 ("effects on work already performed under the original requirement need design"); recovered baseline l.122–126 (autoUpdate "retain source/shown-evidence provenance", no statement that revisions are written); FEAT-001 README l.273 (createdByAction: impact-update). Choose the derived model and remove the other: the FormPublishOperation records {form version, per-question treatment, per-category treatment, frozen impact manifest (audit of what was affected at publish time, for PS3 reproducibility)} and writes no session versions and no answer revisions. Session effective state (Complete under v1 but needs updating for v2; pinned under old version; qualifying) is a derived function of (latest explicit version, its pins, the current published version, the recorded policies, the E23 validity evaluation). autoUpdate means "a pinned revision in the same class with a valid value satisfies the new requirement", not a new revision. The only publication-time writer is Q-34 option mapping, which writes derived revisions with authorship = policy-derived and provenance, excluded from SF5 flags. FV4 becomes a superseding policy record; a late Save is accepted pinned to the version its client declared and is evaluated under the policies at read. Rewrite AC-R2c-02/-05/-06 and AC-R2d-06 accordingly and delete the C5 "policy transition" transition.
VA-04 Major PKG/domain-model.md l.104, l.220; PKG/contracts.md C9 l.371; PKG/ui-coverage-comparison.md l.101, l.189–190; PKG/reviews/review-resolution-matrix.md B-28 l.147 Keying the reconciliation task by "version-compatibility class" is wrong at form grain and contradicts RE4. Compatibility is per question (VA-01). A form v2 that changes five questions (three compatible, two not) would put a v1 candidate and a v2 candidate in different "form classes", creating two tasks for one study × form, two Reconcile entries and two gold publications, against RE4 ("one reconciliation task for a given study and annotation form") and the IA's single entry. v10 already handles this per question ("candidates answered question X on different versions… held"). Ledger RE4 l.655–661; V10/RECONCILIATION.md l.47–50; AC-R4a-09 ("a target-1 form creates no task") assumes one task per form. Task key = (study, form). Pin the form version and every qualifying candidate session version. Per question, derive a held state when the candidates' pinned revisions fall in different classes (or a candidate's value is invalid under the task's form version); held questions block only themselves (prefill, agreement) and show the v10 banner; "Ask vN reviewers to update" raises a Needs-updating request on those sessions. Remove the class from the natural-key list (domain-model l.220) and from C9.
VA-05 Major PKG/contracts.md C4 l.198 ("options" as version content), l.212–214 (Q-34); PKG/open-questions-and-assumptions.md Q-34 l.63; PKG/migration-adoption-rollback.md l.94, l.98 Options have no identity across versions, so a rename silently re-means stored answers and option mapping (Q-34) has nothing stable to map. Today an option answer is stored as the option's value string (StringAnnotation.Answer, StringArrayAnnotation.Answer); schema-v1 options are keyed only by Value; schema-v0 has a stable OptionInfo.Id that the setter preserves only by matching the old value, and v0 conditional targets reference _v0OptionId while v1 targets reference value strings. Renaming "Standard Error" → "SEM" therefore makes every existing answer match no option, which the per-answer validity check (VA-01) would report as invalid, while a meaning change that keeps the value (e.g. redefining "Other") is invisible. AG2/AG3 would compare strings across versions. The classification research already asked for explicit versioned option identity and for rename to be distinguished from meaning change. CORE/StudyAggregate/Annotation.cs l.104–123 (StringAnnotation.Answer string), l.192–206 (StringArrayAnnotation); CORE/ProjectAggregate/OptionInfo.cs QuestionOption.Value (l.~105), OptionInfo.Id (l.~115); CORE/ProjectAggregate/AnnotationQuestion.cs l.262–290 (v0 setter retains Id only by Option == qo.Value); CORE/ProjectAggregate/Target.cs v0 _v0OptionId vs v1 TargetParentOptions (strings); PLN/unified-annotation-classification-research.md l.186. In C4: every option carries a stable optionId within its question; a version lists {optionId, value, description, parentFilter, state: active|retired}; canonical answers store option IDs (values are display); renaming the value keeps the ID (compatible), retiring an option or minting a new ID for a changed meaning is incompatible unless a Q-34 mapping {oldOptionId → newOptionId} is recorded on the publication. Conditions and parent filters reference option IDs. Adoption (R6) mints IDs per legacy value (reusing v0 OptionInfo.Id where present) and maps answers by value. Add AC: "renaming an option's label never invalidates an answer; retiring an option does".
VA-06 Major PKG/domain-model.md l.76 ("ordered question-version references including ancestors"); PKG/contracts.md C4 l.201, l.205–208 (E23); PKG/acceptance-criteria.md AC-R2a-14, AC-R3a-08 Form-version composition has no coherence rule for independently versioned ancestors and children. A form version pins each question at its own version, but a child's conditional-parent condition and an option's parent filter reference the parent's options (today by value or v0 ID). If the form pins child Q v1 (condition on parent option "A") and parent P v3 (option "A" retired), the condition dangles; E23 then cannot evaluate applicability. FEAT-001's parent-integrity rule covers presence of ancestors only. Cycle rejection exists for steps (AC-R3a-08) but there is no composition validation for forms. CORE/ProjectAggregate/Target.cs (OptionConditionalTargetParentOptions.Validate → ParentAnnotationQuestion.ValidateOption, validated against the live parent today); CORE/ProjectAggregate/OptionInfo.cs OptionParentFilter.Validate; FEAT-001 README l.244 (presence-only integrity). Add to C4: a form (or profile) version is composable only if, for every pinned child version, every conditional-parent reference and parent filter resolves to an active option ID in the pinned parent version; otherwise the designer must pick a compatible parent version or a new child version. Record the validated applicability graph in the form version. Add AC-R2a: "composing a form that pins a child condition on an option absent from the pinned parent version is refused with the offending question named".
VA-07 Major PKG/domain-model.md l.76 (form version contains "minimum target, reconciliation compare settings, gold-completeness policy"); PKG/contracts.md C4 l.201, C8 l.342–343; PKG/acceptance-criteria.md AC-R2a-14 Operational form settings are inside the immutable requirement version, so changing a target or compare setting is a "new form version" with the full FV2 impact flow. C8 treats "form target, binding, publication policy" as definition moves under the rewrite fence (i.e. not versions), while the domain model makes target part of the immutable version; the two cannot both hold. SF2 only says the form owns its target; nothing requires the target to be a pinned requirement on sessions (sufficiency reads the current target, as SessionCountTarget does today). Ledger SF2 l.37; SF4 l.414–419 (target is a minimum, so a task is largely target-independent once open). Split: AnnotationFormVersion = question pins (with ancestors), per-question requiredness and the validated applicability graph (immutable, triggers FV2). Form settings = target, reconciliation compare settings, gold-completeness policy (Q-04), guidance (A-15), bulk-approve flag (Q-11): mutable with audit history, read live, never a publication. Apply the same split to profiles (VA-19) and stage settings (VA-08). Reword AC-R2a-14 to "a form requirement version in use can't change".
VA-08 Major PKG/domain-model.md l.81 (StageSettingsVersion bundles "allocation and batch settings; assignment expiry default"), l.61 (StageAllocationRegime versioned separately); PKG/contracts.md C6 l.288, C3 l.161 (stage-settings version on every revision); ledger PV2 l.40, RX2 (frozen bindings) Two problems with stage settings versions. (a) Allocation, batch and expiry settings are bundled into the immutable version although allocation already has its own versioned regime aggregate and RA3 says changed expiry defaults apply to new assignments only; every workload tweak would mint a stage-settings version that PV1 then stamps on every revision as "the requirements the work was done under". (b) PV2 "stage settings bind form versions" plus SF1 "one session per study and form" leaves the session's pinned form version and the route's bound form version free to disagree: a Completed stage A (frozen binding F v1) and an active stage B (F v2) reach the same session; which version does it pin, and what does A show? The plan only notices this for tasks (C9 via B-28), not for sessions. Ledger RA3 l.251–254; PV2 l.40 ("The exact adoption and compatibility transition is not yet decided"); C7 row on proportional allocation (PKG/contracts.md l.328). (a) StageSettingsVersion = bindings, steps, dependency edges, route policies, VS1/BL1/EW1 defaults only; allocation, batches, expiry stay in their own aggregates/settings with audit. (b) Add a binding rule: a stage binds the form and records the version it bound and when; the live route always presents the session's form version resolved as in VA-10; a Completed stage's frozen binding governs only historical display and readiness evaluation for that stage's reports. Put the choice to Chris (question 5 below) because PV2's wording admits both readings.
VA-09 Major PKG/contracts.md C4 l.199 ("An immutable snapshot per question, system-question version and code revision… a code change publishes a new system version through the normal impact flow (E24)"); PKG/open-questions-and-assumptions.md E24 l.126; PKG/domain-model.md l.75, l.95; PKG/reviews/review-resolution-matrix.md B-27 l.146 System-question snapshots keyed by code revision make every CAMARADES wording fix a forced per-project FV2 publication, and the identity problem is deferred rather than solved. System questions are rebuilt from code on every read with fixed GUIDs shared by all projects, and their structure (parent, option filters of the error-type question) depends on Project.SystemQuestionVersion. Under D38 (parent is identity) the v0 and v1 error-type questions are different questions sharing one GUID. "Snapshot per code revision" has no publishing admin for a platform change and would fan out an impact prompt to every canonical project on deploy. CORE/ProjectAggregate/AnnotationQuestion.cs l.396–433 (fixed GUIDs), l.559–592 (parent and filters vary by SystemQuestionVersion), l.813–816 (SystemQuestions(project) built from code); CORE/ProjectAggregate/Project.cs l.223–247 (rebuilt per read), l.475. Model system questions as system-scoped QuestionDefinitions stored as data (seeded idempotently from code, never rebuilt per read), with identity (systemGuid, SystemQuestionVersion) so the v0 and v1 structural variants are distinct identities, and with ordinary content versions published by CAMARADES carrying the VA-01 compatibility flag. Project forms pin a system version like any other; a new system version is available and reaches a project only when its admin publishes a form version (default doNothing). Drop "code revision" from the key. Add AC: "deploying a new system-question version changes no published form and prompts no project admin".
VA-10 Major PKG/open-questions-and-assumptions.md E1 l.103; PKG/contracts.md C5 l.265–266, C4 l.221–223; PKG/acceptance-criteria.md AC-R2c-05 There is no "session upgrade" transition, so sessions pinned under an old form version after doNothing are stuck or silently rebased. When a reviewer Saves a session whose latest explicit version pins F v1 after F v2 was published, the plan does not say which form version the new session version pins. Pinning v2 is the rebasing C4 forbids; pinning v1 keeps producing v1 work indefinitely with no path to v2. E1 defers this to F1/F2 but the rule is a model rule, not mechanics. Ledger SL3 l.44; publish-pause section l.235–243 ("retain the draft and request reviewer action rather than silently rebasing"). Add to C5: an explicit Upgrade transition (reviewer-initiated from the Needs-updating banner, implied by Fix, or offered on the next explicit Save) creates a new incomplete version pinned to the current published form version with the same revision pins; all Needs-updating marks (VA-01 validity, requireReanswer) are shown; nothing is rebased because pins are unchanged. A late Save that declares base v1 is accepted pinned to v1 (never rebased) and evaluated under the recorded policy (VA-03). Under doNothing the admin's count/don't-count choice applies to v1-pinned Completes until upgraded. Add AC-R2c: "Save on a v1-pinned session after v2 publication pins the version the client declared; upgrade is explicit and keeps pins".
VA-11 Major PKG/contracts.md C9 l.373 (drift list), l.375–377 (gold, first publisher wins); PKG/domain-model.md l.107; ledger GS1 l.41, RE4 l.660–661 Gold and open tasks have no rule for definition changes. (a) A gold answer pins a reconciled revision under Q v1; after F v2 publishes Q v2 as incompatible, the snapshot is still current (GS1, correctly) but the gold answer no longer satisfies the form's requirement; nothing flags it and exports/PRISMA would present it as current gold for v2. (b) The task drift list covers candidate changes but not a publication during an open task: under SF6 a requireReanswer policy de-qualifies pinned candidates immediately, which contradicts v10's "their last completed review keeps counting until they submit"; the plan follows neither explicitly. © "First publisher wins" discards the second form's candidates (often different reviewers) from the gold decision for a shared question and routes their disagreement into the query process designed for post-hoc challenges. V10/RECONCILIATION.md l.48–49; ledger SF6 l.533–537; V10/DECISIONS.md RD13 l.320–326 ("re-checks after a correction become v2.1"). Add to C9: (a) a derived goldNeedsReReconciliation(question) state when the current form pin's class differs from the gold revision's class; gold stays effective (QY1 analogue) and is labelled with its version in exports and PRISMA manifests; (b) "publication policy de-qualifies a pinned candidate" joins the drift triggers → "inputs changed · re-check", never retraction; © replace first-publisher-wins with "existing shared gold is prefilled as accepted in the second task; the second reconciler may revise it in their final submission, producing a new snapshot with provenance"; queries remain for everyone else (put to Chris, question 4).
VA-12 Minor PKG/open-questions-and-assumptions.md E21 l.123 ("one draft per session"); PKG/contracts.md C5 l.251–253; PKG/acceptance-criteria.md AC-R2a-06 l.159 ("both drafts are kept") Draft cardinality is contradictory. One draft per session cannot "keep both" when two tabs conflict; the research explicitly allows a draft per stage workspace, each pinning its base revision. PLN/screening-specialised-annotation-research.md l.702–707 ("A reviewer can have drafts for the same shared answer in two stage workspaces. Each draft pins a base revision"). Define: one draft per (session, workspace lease); a Save from a stale lease gets a typed conflict and its draft is retained for comparison; discard is per draft and audited. Reword E21 and AC-R2a-06 to match.
VA-13 Minor PKG/open-questions-and-assumptions.md A-14 l.196; PKG/acceptance-criteria.md AC-R2a-14 l.167; PKG/domain-model.md l.76 "In use" / "first use" is undefined. Is a form version in use when a draft-only session exists, when the first Save happens, or when it is bound to an active stage? The research warns against turning an unedited open into a persistent session and says first use locks the definition "as used". PLN/screening-specialised-annotation-research.md l.666–668, l.942–943. Define "in use" = any FormSession (including draft-only) or any ReconciliationTask references the version, or it is bound by an Active stage settings version; a draft-only session created against a version that is then edited gets a typed stale-definition conflict (research A16). Put the definition in C4 and AC-R2a-14.
VA-14 Major PKG/contracts.md C4 l.212–214; PKG/acceptance-criteria.md AC-R2c-02 l.190; PKG/ui-coverage-comparison.md l.72 The three recovered treatments do not cover FV1's headline case (an added question) or removed questions. requireReanswer/autoUpdate/doNothing are defined for changed questions. For a newly added required question there is nothing to re-answer or carry; the admin's FV3 choice is whether earlier Completes still count (with the new question blank, never N/A). For a removed question (or one that becomes inapplicable through an ancestor change) the answers stay pinned in old versions but the dialog has no treatment. FEAT-003's classification has "New question" and "Removed" rows; the package's vocabulary mapping dropped them. Ledger FV1 l.48, FV3 l.50 (counting follows the choice; "a missing Q3 answer is not manufactured"), l.340 ("No universal count default remains to ask Chris"). FEAT-003 README l.258–260. Extend the per-question treatment vocabulary: added → {count earlier Completes as satisfying v2
VA-15 Minor PKG/integrated-plan.md l.148–149, l.402–405; PKG/contracts.md C4 l.225–227; PKG/domain-model.md l.75; PKG/acceptance-criteria.md AC-R2a-10 QD1 wording conflates retiring a question with removing it from a form, and "published" is undefined for questions. "Retired in a new version" is ambiguous: a question status (no new forms may use it) vs a form version that no longer references it. Since publication is a form-level act, a question version committed in the designer but never referenced by a published form or profile version is unpublished and deletable; the plan does not say so. As cited. State: a question is published once any of its versions is referenced by a published form or profile version; thereafter it can be removed from forms (form versioning) or retired (status, blocks new use) but never deleted; a committed-but-unreferenced question version, and a pending edit, may be discarded. Update AC-R2a-10.
VA-16 Major PKG/contracts.md C4 l.189–195 (adopt D38 as PROPOSAL); PKG/domain-model.md l.75 ("multiplicity" structural) D38 as proposed forces whole-subtree identity churn for ordinary protocol refinements. With parent and data type and multiplicity as identity, turning a single-select into a multi-select, or fixing a parent's type, creates new question identities; because parent is identity, every descendant must also be recreated, severing SF5 lineage and all answer history for the subtree. QM v2 D008/K007 argued data type is content because revisions pin their version. The plan picks D38 without addressing this cost, and Q-34 shows transformations are wanted. MAIN/docs/features/annotation-versioning/design-session.md l.355 (D38), l.90–92; MAIN/docs/planning/qm-v2-context/qm-v2-architecture-and-knowledge.md l.36 (D008), l.446–470 (K007 rationale); ledger SF5 l.452–458 (ancestor lineage). Recommend identity = {questionId, ownerScope, parent, entityType}; data type, multiplicity, options, wording, help, validators, conditions are version content, with data-type and multiplicity changes always classified incompatible (VA-01) and the revision payload type pinned to its version. Lineage and history survive; agreement never crosses the class. Put to Chris (question 2).
VA-17 Minor PKG/contracts.md C11 l.441–443; PKG/acceptance-criteria.md AC-R2a-05, AC-R5a-01…06, AC-R5c-01 Mixed-version data has no export or statistics rule. Wide (one column per question) exports with option renames or incompatible versions produce columns of mixed meaning; "version identifiers" per export are not enough. Usage statistics (C8) count sessions per form version, but a session can pin revisions of several question versions (shared heads), so per-question-version usage must be per revision, not per session. V10/DECISIONS.md RD13 l.320–326 (version column), RD14 l.328–334 (compatible versions pooled); ledger AG3. Add C11 rule: every exported answer carries (questionId, questionVersion, classId, optionId); wide exports are generated per form version or per class with a per-cell version column; manifests list definition versions. Add C8 rule: question-version usage is counted from revisions, form-version usage from session versions. Add AC-R2a-05b and AC-R5c-05.
VA-18 Note PKG/migration-adoption-rollback.md l.98 (definitionVersionAtAuthoring = unknown, "unless the wording is proven unchanged") The "unknown" rule is justified (verified: the legacy upsert validates placement for new questions only and applies no lock on answered questions, so wording can change after answers), but the package misses that each legacy answer already stores the wording it was authored under, which is exactly the proof the rule asks for. CORE/ProjectAggregate/Project.cs l.489–514 (placement validation only when IsNew); CORE/StudyAggregate/Annotation.cs l.25, l.41 (Question snapshot); CORE/StudyAggregate/AnnotationOptions.cs l.16, l.30. In E10: compare Annotation.Question with the adopted v1 wording; equal → pin to v1 as "wording verified"; different → unknown and excluded from same-version agreement. Add an R6 fixture for both outcomes.
VA-19 Major PKG/domain-model.md l.78 (profile version bundles routes, must-agree set, DP5 toggle, rationale settings); PKG/contracts.md C4 l.202; PKG/open-questions-and-assumptions.md Q-26 l.66 Profile versions bundle scientific criteria with operational routes, so toggling DP5 or changing a resolution route forces a profile publication and a Q-26 decision over every cast decision. DP5 "Off keeps recorded reasons and history" is operational; RX1 routes are operational. Only eligibility questions, decision rules and the must-agree set are requirements that cast decisions pin. Ledger DP5 l.638–653; DP4 l.573–592; RX1 l.603–636. Split as in VA-07: ScreeningProfileVersion (eligibility question pins, decision rules, must-agree set; pinned by decision revisions; Q-26 impact) vs profile settings (DP5, routes, rationale, PRISMA phase mapping version reference) with audit history. Reword Q-26 to apply to criteria versions only.
VA-20 Note PKG/domain-model.md l.92 (ScreeningOutcome "append-only outcome versions… the current outcome is projected onto Study") The outcome is a deterministic function of decision revisions, adjudications and the profile version; storing it as an aggregate with its own version history is double storage of derived data. Acceptable for as-of PRISMA performance, but it must be declared rebuildable, as the research says, or it becomes a second source of truth. PLN/screening-specialised-annotation-research.md §3.10 table ("Screening outcomes: rebuildable per-study/profile projection with input version; never the only copy of decisions"). Label ScreeningOutcome a materialised projection keyed by input commit sequence, with a rebuild command and a parity fixture against recomputation.
VA-21 Note PKG/contracts.md C11 l.444–448, C3 l.164 The model is transaction-time only (commit sequence); valid time exists only as provenance timestamps with trust levels. That is the right choice, but it is never stated, and as-of exports could be misread as "what was true then" rather than "what SyRF knew then". Research AnnotationRevision { recordedAt, observedAt? } (PLN/screening-specialised-annotation-research.md l.636–641). State in C11: as-of = transaction time by commit sequence; observedAt/legacy DateTimeCreated are evidence fields never used for ordering.
VA-22 Major PKG/domain-model.md l.90 ("revisions submitted together"); PKG/contracts.md C3 l.162, C5 l.258–260; ledger PV1 l.39; PKG/open-questions-and-assumptions.md E28 l.130 Whether a session version pins the full answer set or only the delta is unspecified. FEAT-001's ASV pins the complete AnnotationAVMap ("no computed filters or 'latest' lookups"); "revisions submitted together" reads as the delta. Previous-version export, reconciliation candidate pinning, gold pins and "revisions pinned by gold are never deleted" all need the full set; a delta chain makes every such read a chain walk and makes E28's size ceiling a different problem. FEAT-001 README l.296–303, l.590; research ReviewSubmission.annotationRevisionRefs[] (§3.6). State in C5: a session version pins the complete map of (head → revision) for every answer in the session at that time; storage may delta-encode behind the aggregate, but the logical contract and E28's ceiling are defined on the full map. Add AC-R2a: "a previous-version export of a session equals the full map pinned at that version".
VA-23 Minor PKG/domain-model.md l.109; PKG/contracts.md C9 l.381 ("one per accepted-answer version") "Accepted-answer version" is undefined: a reconciled revision ID (shared unchanged across many snapshots) or a (snapshot, question) pair. Only the former gives "one work item per answer version" as the ledger intends. Ledger QY2 l.208–211. Define the query target as the reconciled revision ID; QY9's "current applicability" compares the target revision to the snapshot's current revision for that head and to the form's current question class (VA-11a).
VA-25 Minor PKG/domain-model.md l.75 ("entity category or type" structural), l.115 (legacy categories become system entity types with alias); PKG/contracts.md C13 Category is declared structural identity, but C1 renames categories to entity types; by the plan's own rule that would be a new identity. As cited. State that the structural property is the entity-type ID; the legacy category string is a display alias mapped at C1 with no identity change (consistent with AC-C1-02).
VA-27 Minor PKG/contracts.md C17 l.591–593 (copy contract), C2 l.147–149; ledger VU1 l.186–191, SF5 l.466–468, SF6 l.527–531 Three distinct answer states are named in copy but not modelled: Needs updating (definition changed: incompatible class or invalid value), contains outdated annotations (reviewer's own newer revision exists in the same class), and pinned under an older version (doNothing; no action required). Without the state machine, the UI cannot explain which version counts, which is R2a's user-testing exit criterion. PKG/acceptance-criteria.md l.418 (R2a task: "say which version counts and why"). Add to C5 a derived per-answer state enum {current, outdatedOwnAnswer, needsUpdatingVersion, needsUpdatingValue, pinnedOlderVersion, notApplicable} and a per-session effective state derived from it; make U13 validate all six.

3. Improvements

  1. Write a one-page "versioning rulebook" and attach it to C4/C5. One table: version kind (question content, option, form requirement, form settings, profile criteria, profile settings, stage settings, stage lifecycle, session version, draft, revision, gold snapshot, outcome projection, policy record, system question), its identity, what pins it, what is immutable, what is derived, what triggers an impact flow. Most of the findings above are places where the package has not decided a cell of that table.

  2. Simplest model that satisfies the ledger (recommended, replacing nothing the owner decided):

  3. Question: identity {id, ownerScope, parent, entityType}; versions carry all content including data type and multiplicity; options have stable IDs; each version has an immutable-once-used compatibleWithPrevious flag; classes derived (VA-01, VA-05, VA-16).
  4. Form: requirement versions = ordered pins with ancestors, requiredness, validated applicability graph; settings separate (VA-06, VA-07). Profiles and stages split the same way (VA-08, VA-19).
  5. Head key includes the class; revisions pin exact versions; one linear current pointer per head (VA-02).
  6. Session versions pin the full map; drafts per workspace; status, qualification, Needs updating, outdated and pinned-older are all derived (VA-03, VA-12, VA-22, VA-27).
  7. Publication = policy record + frozen audit manifest; writes no evidence; Q-34 mapping is the only derived-revision writer (VA-03, VA-14).
  8. Task per study × form with per-question held state; gold snapshot per study with per-answer re-reconciliation flags (VA-04, VA-11). This is a transaction-time, revision-log model with derived projections. It does not need full event sourcing (commands already produce receipts and a total order; no replay is required), it does not need bitemporal modelling (valid time is provenance only, VA-21), and it should not fall back to per-form snapshots (SF3 and DP4 need question-level identity and compatibility; content-hash equality would be fragile). Keep per-question versions.

  9. Two-step publication, stated once. Committing a question version in the designer has no session impact; publishing a form or profile version is the FV2/Q-26 impact point; publishing a system question version (CAMARADES) has no project impact until adopted (VA-09, VA-15). This removes the ambiguity in C4's "Input: a draft definition".

  10. Derive rather than store (each is cheaper and cannot drift): policy effects on sessions (VA-03); Needs-updating/outdated flags (E30's "outdated-flag fan-out" becomes an indexed read over pinned revisions plus the head's last commit sequence); the task's compatibility class (VA-04); ScreeningOutcome history (VA-20); PrismaPhaseMapping as a profile-settings field rather than its own aggregate (PKG/domain-model.md l.83). Keep stored: revisions, session versions, gold snapshots, policy records, commit sequence.

  11. Conformance fixture set for versioning (add to C4/C5 suites at F1): compatible-added-option shared across two forms; v2-only option never shown under v1; two forms on incompatible versions of one question never flag each other; Fix shows in-class current; late Save pinned to the declared version; upgrade keeps pins; publication of a 10,000-session form writes no session versions; FV4 supersession restores counting without touching versions; option rename keeps answers valid, option retirement does not; composition refusal for a dangling condition; gold flagged for re-reconciliation after an incompatible publication; task per form with one held question; wide export with mixed versions; legacy wording verified vs unknown via Annotation.Question.

  12. Use the stored answer wording as adoption evidence (VA-18) and reuse OptionInfo.Id as the option ID for v0 projects.

  13. Make the publication dialog (U6) show the per-question state vocabulary, not just the three recovered choices: added, removed, changed-compatible, changed-incompatible, with the system suggestion and the per-category counts; this is where VA-01, VA-05 and VA-14 become visible to admins.

4. Questions for Chris

  1. Compatibility authority and timing. Should a question version's compatibility with its predecessor be declared when the version is committed (system-suggested, admin-confirmed) and become immutable once any answer pins it, with FV4 revising only the counting/re-answer policy of a publication, never the compatibility declaration? This rules out v10's later "Mark compatible" on held answers except before any answer exists under the new version. Recommendation: yes; it keeps classes stable so heads, agreement and gold never need re-keying.

  2. D38 or D008 for data type and multiplicity. Should changing a question's data type or single/multi-select shape be a new question identity (D38, which recreates the whole subtree under the "parent is identity" rule) or an incompatible version of the same identity (preserving lineage and history, never compared or carried across)? Recommendation: incompatible version of the same identity; keep parent and owner scope as identity.

  3. Publication never writes evidence. Confirm that autoUpdate means "a pinned, compatible, valid answer is accepted for the new requirement" without writing a new revision, and that only Q-34 option mapping may write policy-derived revisions (attributed to the publication, excluded from SF5 flags). This reads the recovered baseline's "retain provenance" as the pinned revision plus the policy record. Recommendation: yes.

  4. Shared-question gold across overlapping forms. First publisher wins with challenges only by query (the plan's proposal), or the second task sees existing gold prefilled as accepted and may revise it in its own final submission, producing a new snapshot with provenance? Recommendation: the second; it keeps the second form's candidates (often different reviewers) in the gold decision and reserves queries for post-hoc challenges.

  5. What "stage settings bind form versions" (PV2) means. Does a stage pin a specific form version that it presents (so two stages can present different versions of one form to the same reviewer-owned session), or does a stage bind the form and record which version was bound when, with the live route always presenting the session's resolved version and Completed stages frozen for display and readiness only? Recommendation: the second; it is the only reading consistent with one session per study and form (SF1) and per-form publication (FV2).

  6. System question versions. Should CAMARADES-published system question versions reach a project only when its admin next publishes a form version (default pinned), rather than through a forced impact prompt on deploy? Recommendation: yes; and system questions become stored, versioned definitions with (guid, SystemQuestionVersion) identity.

  7. Operational settings outside requirement versions. Confirm that form target, reconciliation compare settings, gold completeness, guidance, DP5, resolution routes, allocation, batch and expiry settings are audited settings that never trigger FV2/Q-26 impact flows, and that PV1's "stage-settings version" stamp refers to the requirement-bearing part only. Recommendation: yes.

5. Coverage gaps

Missing rules (no owner decision needed, but absent from every contract): - Definition and algebra of version compatibility, and its distinction from answer validity (VA-01). - Version component (class) in the head key, or a per-class current pointer (VA-02). - Option identity across versions and the storage of option answers by ID (VA-05). - Form/profile version composition validity for conditions and parent filters against pinned parent versions (VA-06). - Session upgrade transition and the pin rule for Saves after a publication (VA-10). - Treatment vocabulary for added and removed questions (VA-14). - "In use" definition for form and question versions (VA-13). - Full-map pinning on session versions (VA-22). - Gold re-reconciliation flag and task drift on publication (VA-11). - Query target identity (VA-23). - Derived per-answer and per-session state machine with the three copy-contract states (VA-27). - Wide-export and usage-statistics rules for mixed versions (VA-17). - System question identity across SystemQuestionVersion and the platform publication path (VA-09). - Legacy wording verification via Annotation.Question (VA-18).

Missing acceptance criteria (proposed IDs): - AC-R2a-20: compatibility is declared per question version, immutable once pinned, and classes are transitive (three-version chain fixture). - AC-R2a-21: renaming an option keeps answers valid; retiring one does not; conditions reference option IDs. - AC-R2a-22: composing a form with a child condition on an option absent from the pinned parent version is refused. - AC-R2a-23: a session version's previous-version export equals its full pinned map. - AC-R2a-24: "in use" refusal for a draft-only session and for an Active binding. - AC-R2c-10: publication writes no session versions and no answer revisions; derived states appear immediately; the 10,000-session fixture runs in constant time relative to session count for phase 2. - AC-R2c-11: added-required and removed questions are listed with their own treatments and counting outcomes. - AC-R2c-12: Save on a v1-pinned session after v2 publication pins the declared version; Upgrade keeps pins and shows Needs updating. - AC-R2c-13: FV4 supersedes a policy record and restores qualification without touching versions or work. - AC-R2c-14: deploying a new system-question version changes no published form and prompts no admin. - AC-R2d-09: two forms pinning incompatible versions of one shared question never flag each other; a v2-only option never appears under v1; Fix shows the in-class current revision. - AC-R2d-10: the six per-answer states render and are explained (extends U13). - AC-R4a-14: one task per study × form; a question whose candidates span classes is held individually; the rest reconciles. - AC-R4a-15: an incompatible publication flags affected gold answers for re-reconciliation; gold stays effective and labelled. - AC-R5a-07 / AC-R5c-05: exports carry per-cell version and option IDs; agreement never crosses a class and flags differing versions within one. - AC-R6-07: adopted answers are pinned "wording verified" when Annotation.Question equals the v1 wording, otherwise unknown.

Gate timing: VA-01, VA-02, VA-05, VA-16 and VA-22 change C2/C4/C5 shapes and must be settled before F1, not at F2 where the package currently places compatibility and publication detail.

Critical files for implementation

  • /home/chris/workspace/syrf/pr/pr3617.research-screening-as-specialised-annotation-gxgahs/docs/planning/integrated-review-plan-2026-10/contracts.md
  • /home/chris/workspace/syrf/pr/pr3617.research-screening-as-specialised-annotation-gxgahs/docs/planning/integrated-review-plan-2026-10/domain-model.md
  • /home/chris/workspace/syrf/pr/pr3617.research-screening-as-specialised-annotation-gxgahs/docs/planning/integrated-review-plan-2026-10/acceptance-criteria.md
  • /home/chris/workspace/syrf/main/src/libs/project-management/SyRF.ProjectManagement.Core/Model/ProjectAggregate/AnnotationQuestion.cs
  • /home/chris/workspace/syrf/main/src/libs/project-management/SyRF.ProjectManagement.Core/Model/StudyAggregate/Annotation.cs