Skip to content

Versioning model

Temporary planning document; planning only. This is the one rulebook for versioning in the integrated review programme. It answers the round-2 reviews VA and VB and the versioning findings of the DC, DD, PH, SR, MS and V2 reviews. Contracts C2, C3, C4, C5, C9 and C11 in contracts are amended to match it. Those amendments, and this model's changes to the integrated plan (R2a–R2d, R4a), open questions (the rewritten engineering rows, E36–E45, A-14, A-25 and A-26) and acceptance criteria (release criteria and the §7.5 conformance tests), have been merged into those documents. Mechanics the model relies on but does not own (transactions, fences, the command ledger, ownership markers, durable effects, ordering and the Study canonical summary) live in the consistency model. Where this document needs one of them it states the versioning rule and links to the section that implements it.

Labels: OWNER (ledger ID or register §1.11 ID), RECOVERED (existing design documents or code, not a new owner decision), PROPOSAL (this plan's recommendation), OPEN (Q-xx), ASSUMPTION (A-xx), CODE-MAIN (verified on main at 0f5c61073; file and line cited). A rule with no label is a PROPOSAL. Nothing here reopens a confirmed owner decision; where a decision has consequences, the consequence is stated. Anything that needs Chris cites a Batch D question from the resolution brief; this document mints no question IDs.

1. Purpose and status

1.1 What this document decides

The owner decisions fix the shape: forms own evidence and stages own workflow (SF1, SF2), the latest explicit Save or Complete is current (SL3), publication checks every prior version and records an admin choice (FV1–FV3), compatible same-context answers are shared across forms (SF3, SF5), gold is an immutable snapshot (GS1), and a published question is never deleted (QD1). The reviews found that the package used "compatibility" in four senses, keyed shared answers without a version component, modelled publication two incompatible ways, over-stuffed version containers with operational settings, and left options, entity instances, system questions, drafts and legacy duplicates without identity rules. This document decides each of those cells once.

The model in one paragraph: every definition and every piece of evidence has a stable identity and append-only immutable versions; every version, revision, session version and snapshot pins the exact versions it depends on by ID; a compatibility class derived from per-version declarations says which question versions mean the same thing; derived states (current, outdated, needs updating, qualifying, held, needs re-reconciliation) are computed from pins, classes and recorded policies and are never stored as facts; publication is a recorded policy, not a writer of evidence; and operational settings change with an audit trail and never trigger an impact flow. Time is transaction time only.

1.2 Status of each part

Part Status
Shared sessions, SL3, FV1–FV4, VU1–VU3, PS1–PS3, SF3–SF6, PV1, PV2, GS1, RE2, RE4, RE5, AG2, AG3, QY1–QY9, DP4, DP5, QD1 OWNER; restated, never reopened
Per-question requireReanswer / autoUpdate / doNothing; BreakingChange declared at version creation; breaking transitivity; FEAT-003's per-session categories; stored answer wording (Annotation.Question); the 25 September adjudication precedence rule RECOVERED (sources cited where used)
Compatibility algebra, class in the head key, option identity, requirement versus settings split, system questions as data, Upgrade, treatment vocabulary, per-answer state enum, entity-instance rules, storage rules, task held state, gold re-reconciliation PROPOSAL; the parts that need Chris are listed in §15.1 with their Batch D IDs
Option mapping (Q-34), profile-version transition (Q-26), gold completeness (Q-04), target-1 forms (Q-29), legacy reconciled answers (Q-35), self-reconciliation (Q-36) OPEN; this document says what holds until each is answered

1.3 Code baseline

Code claims are verified on /home/chris/workspace/syrf/main at 0f5c61073 unless marked UNVERIFIED. The files that matter here are src/libs/project-management/SyRF.ProjectManagement.Core/Model/ProjectAggregate/AnnotationQuestion.cs, OptionInfo.cs, Target.cs, Project.cs, StudyAggregate/Annotation.cs, AnnotationOptions.cs, AnnotationSession.cs, ExtractionInfo.cs, src/libs/mongo/SyRF.Mongo.Common/MongoExtensions.cs and MongoContext.cs, and src/services/web/src/app/shared/annotation/annotation-form-v2/annotation-form-v2-eligibility.ts.

2. Version kinds rulebook

One row per kind. "Identity" is what never changes. "Pinned by" says who references the exact version. "Immutable" says what may never be rewritten. "Derived" says what is computed on read and never stored as a fact (a materialised copy may exist only with its input-version vector, consistency model §8). "Impact flow" says whether a change can start the FV2/Q-26 admin flow.

Kind Identity Pinned by Immutable Derived Impact flow
Question definition QuestionRef = {questionId, systemQuestionVersion (system questions only)} plus structural identity {definitionOwner (project or profile), parent QuestionRef, entityTypeId, repeatable} (§3.1) Versions, heads Identity; a structural change is a new question Status (draft, published, retired) from references (§3.8) Never at commit; only through form or profile publication
Question content version (QuestionRef, seq) Revisions; form, profile and system versions Content once committed; compatibleWithPrevious once any revision pins it (§3.5) classSeq, class membership, system suggestion None by itself
Option optionId inside its question Revision payloads (by ID); conditions and parent filters (by ID) The ID; per-version content Validity of answers that select it Retirement or meaning change makes the version incompatible by default (§3.5)
Form requirement version (formId, seq) Session versions, tasks, stage settings versions, publication records Everything once published: ordered pins with ancestors, requiredness, applicability graph, renderability result Usage, impact categories Publication starts the FV2 flow
Form settings formId head Nothing Nothing; every change appends an audit entry (§4.4) Sufficiency and readiness read the live value Never
Profile criteria version (profileId, seq) Decision revisions, profile-owned answer revisions, stage settings versions Everything once published: eligibility question pins, decision rules, must-agree set, requiredness Decision standing under Q-26 Profile publication starts the Q-26 flow
Profile settings profileId head Nothing Nothing; audited (§5.1) Routes and DP5 read live Never
Stage settings version (stageId, seq) Revisions (PV1 provenance), session versions (route), tasks (route) Bindings, steps, dependency edges, route policies, VS1, BL1, EW1 defaults, filter element Active, from lifecycle status Never for sessions; a new binding is an admission change (§5.4)
Stage lifecycle stageId Nothing Status events are append-only Active, readiness Never
Session version (sessionId, seq) Tasks, gold (through revisions), exports, manifests Kind (Save, Complete, Withdraw), pinned form version, route, full pin map, resolved question set, exposure state Per-answer state, requirement standing, qualification (§7.4, §7.5) Never
Draft sessionId (one record; bounded conflict copies) Nothing Nothing; lease, etag, patches (§7.6) Draft-changes flag; draft_only category Counted in the manifest, never transitioned
Revision revisionId on a head Session versions, gold snapshots, tasks, queries, policy records (mapping) Everything; append-only Current (relative to pins), outdated, valid under a version Never
Head AnswerContextKey including classSeq (§6.1) Session versions (through revisions) The key; currentRevisionId changes by CAS only Current, outdated flags, conflicted Never
Entity instance Label head ID in the author's scope (§6.5) Context keys (entity path), outcome cells, population membership The ID; rename is a label revision; delete is withdrawal Population membership attribute, order (presentation) Never
Entity type entityTypeId (system IDs minted at F1a, §3.1) Question identity The ID; legacy category string is a display alias Capabilities (C1) Never
Gold snapshot (studyId, seq) Queries, exports, PRISMA manifests Entries (question context, reconciled revision ID), provenance goldNeedsReReconciliation per entry (§9.4), pending-query flag Never
Reconciliation task input set (taskId, seq) Gold snapshot provenance Pinned form version and candidate session versions per input set Held per question, drift state (§9.2, §9.3) Never
Screening outcome projection (studyId, profileId) Nothing Nothing; rebuildable with its input-version vector Entirely (consistency model §8) Never
Publication policy record (operationId, generation) Nothing; read by derivation Each generation once written; FV4 appends a generation (§8.7) Every effect on sessions This record is the impact flow
System question version (systemGuid, systemQuestionVersion, seq) Form versions, revisions Everything once published by CAMARADES Class, as for any question None until a project form publishes a pin (§3.7, D2-06)
Template (templateId, seq) Nothing after copy; copies record the source (templateId, seq) Everything once published Nothing Never; a copy never changes with its template (DP4)
Claim Not a version kind. Claims are keyed by form or profile identity, never by version (brief §2.1, RT-11)

Rules that hold for every kind:

  1. Identity never changes. A structural change creates a new identity; lineage is recorded by reference (derivedFrom, copiedFrom, supersedes), never by rewriting.
  2. Versions are append-only with a content digest. Nothing published is edited or deleted (QD1, SL2, GS1). Discard applies only to unpublished definition versions and to drafts (§3.8, §7.6).
  3. Pins reference exact versions by ID. A reader never resolves "latest" to find what a stored record meant.
  4. Effects are derived, never written. Publication, option retirement and definition changes change what readers derive; they write no session versions and no revisions, with the single exception in §8.9.
  5. Impact flows start only at form or profile publication. Committing a question version, changing a setting or publishing stage settings never prompts an admin about sessions.
  6. Operational settings never version. They change with an audit entry and are read live (D2-05).
  7. Time is transaction time. Order comes from per-aggregate sequences and the HLC commit stamp (consistency model §11); observedAt and legacy DateTimeCreated are evidence fields and never order anything (§10.5).

3. Questions

3.1 Identity

A question is identified by a QuestionRef value object and a set of structural properties:

Property Role Note
questionId Identity For system questions the fixed GUID from AnnotationQuestion.cs:396-434 (CODE-MAIN)
systemQuestionVersion Identity, system questions only The v0 and v1 structural variants of the outcome error-type question have different parents and option filters (AnnotationQuestion.cs:559-592, CODE-MAIN), so they are different identities that share a GUID. A project has exactly one value (Project.cs:475), so the pair is unambiguous within a project. Null for project and profile questions
definitionOwner Identity project, or profile:<profileId> for eligibility questions (DP4). Named definitionOwner to keep it apart from the revision edge owningParent (§6.4, DD-26)
parent Identity Parent QuestionRef; the subtree shape (FEAT-001 D38, docs/features/annotation-versioning/design-session.md:90, RECOVERED)
entityTypeId Identity The entity type (today's category string). Stable system IDs for the seven legacy categories plus cohort, outcome measure and experiment are minted at F1a as a shared value object of C4 and C13, and the legacy string becomes a display alias with no identity change (VA-25, DD-12). R2a forms pin the ID, so C1 later adds capabilities without re-identifying anything
repeatable Identity Whether the question creates repeated instances (today Multiple && !AnswerArray). It is identity because it changes the shape of the context key (an instance element appears in entityPath)

Data type and selection multiplicity (D2-03). Two designs exist in the earlier documents:

  • D38 (FEAT-001): data type, parent and grouping are identity; changing data type creates a new question (design-session.md:355, RECOVERED). Cost: with parent also identity, every descendant must be recreated, which severs SF5 lineage and answer history for the subtree (VA-16).
  • D008 / K007 (QM v2): data type and multiplicity are version content because revisions pin the version that defines the payload (docs/planning/qm-v2-context/qm-v2-architecture-and-knowledge.md:36, :442-470, RECOVERED).

Recommendation (Batch D D2-03): data type and selection multiplicity (single or multi-select, today AnswerArray) are version content and a change to either is always classified incompatible (§3.5), so lineage and history survive and nothing is ever compared or carried across the change. Parent, definition owner, entity type and repeatability stay identity. Until D2-03 is answered the designer refuses data-type and multiplicity edits on a published question, which is today's behaviour and loses nothing.

3.2 Content versions

A content version is (QuestionRef, seq) with seq sequential from 1 (ASSUMPTION A-25: versions are linear per identity). Content:

Field group Content
Presentation wording, description, help, control type, display labels, option order, answer label (AnnotationQuestion.cs:138-150, CODE-MAIN), category guidance reference
Shape data type, selection multiplicity (D2-03), validators
Options the option list (§3.3)
Applicability conditional-parent condition expressed in parent option IDs or a boolean; option parent filters in option IDs
Extensibility response modes and metadata fields (§3.4)
Change record "Why it changed" and "What reviewers need to do differently" (VU2, optional)
Compatibility compatibleWithPrevious with the system suggestion, the admin's confirmation, an optional rationale, and an optional option mapping (§3.5, §8.9)
Integrity contentDigest, author, HLC stamp

Requiredness is not version content: a form or profile decides whether a pinned question is required (§4.1), so one question can be required in F and optional in G. This differs from today's Optional on the question and from QM v2's AQVersion.Optional (VB-17), deliberately.

Committing a version has no session impact and no admin prompt (VA improvement 3). It makes the version available for form and profile composition. A version that no published form or profile version references may be discarded (§3.8).

3.3 Options and stable option IDs

Today an option answer is stored as the option's value string (StringAnnotation.Answer, Annotation.cs:122; StringArrayAnnotation.Answer, Annotation.cs:210; CODE-MAIN). Schema-v1 options are keyed by Value only (OptionInfo.cs:105); schema-v0 options carry a stable OptionInfo.Id that the setter keeps only by matching the old value (AnnotationQuestion.cs:275-289); v0 single-option conditions reference _v0OptionId (Target.cs:291) while v1 and ADR-011 hybrid conditions reference value strings (Target.cs:50; docs/decisions/ADR-011-schema-v0-multi-option-conditional-parent-answers.md:50-68); parent filters validate against the live parent's values (OptionInfo.cs:51-60). Renaming a value therefore re-means every stored answer, and a meaning change that keeps the value is invisible (VA-05). The classification research already asked for versioned option identity (../unified-annotation-classification-research.md:186).

Rules:

  1. Every option has an optionId (GUID, minted once, never reused) inside its question. A version lists {optionId, value, displayLabel?, description, parentFilter (parent option IDs), state: active | retired}.
  2. Canonical answers store option IDs; values and labels are display data resolved from the pinned version. Exports carry both (§10.2).
  3. Renaming value or displayLabel keeps the ID and is compatible. Retiring an option, or minting a new ID because the meaning changed, is incompatible unless a one-to-one mapping is recorded (§3.5, §8.9).
  4. Conditions and parent filters reference option IDs and are validated against the pinned parent version at composition time (§4.2), not against the live parent.
  5. Adoption mints IDs per legacy value, reusing OptionInfo.Id where a v0 project has one, and maps answers by value (§11).

3.4 Response modes, metadata and suppression

The approved extensibility architecture (docs/features/question-management/annotation-question-extensibility-architecture.md:87-93, :169-191, RECOVERED) defines responses as a value or a response mode, validated metadata, a required reason on modes that need one, descendants that a mode suppresses are preserved and never tree-shaken, exports that must resolve suppression, and a definition-version stamp on every response. It leaves open whether definitions are frozen once used or snapshotted per response. This model settles that question by frozen versions (PH-06):

  • Response modes and metadata fields are version content (§3.2).
  • A revision payload is value XOR responseModeId, plus metadata validated against the pinned version's fields; the definitionVersion stamp is the revision's questionVersionRef.
  • Suppressed descendants are preserved. A canonical Save or Complete keeps every pinned revision whose question an ancestor's answer or mode now suppresses; the per-answer state is NotApplicable (§7.4), derived per ancestor instance, never per question. The legacy path tree-shakes (ExtractionInfo.cs:183-194, CODE-MAIN); the canonical path never does.
  • Exports resolve suppression with an explicit status column or by omission, never by emitting a suppressed value as live (§10.2).
  • "Not applicable" is an explicit response mode. Blank is not N/A (UA1); two explicit N/A answers agree and N/A against a value disagrees (AG3).

3.5 Compatibility

Compatibility is defined once, here, and used by SF3, SF5, FV3, AG3, RE4, RE5 and the head key.

Declaration (D2-02). compatibleWithPrevious(Q, n) is declared when version n is committed in the designer. The system computes a suggestion from the diff with n−1; the admin confirms it, may tighten freely, and may loosen only where the table allows. The declaration is immutable once any revision pins version n. Before that it may change, and the change recomputes the classes of n and its successors (all unpinned) and re-validates every active publication policy that references them; a flip that would make a recorded autoUpdate invalid is refused until FV4 revises the policy. FV4 revises policy, never compatibility.

Change between n−1 and n Suggestion Admin may
Wording, description, help, display labels, control type, option order, answer label, guidance compatible tighten to incompatible (a wording change that alters meaning)
Option added; option value or label renamed keeping the ID; parent filter changed; condition changed; validator loosened or tightened; response mode or metadata field added or loosened compatible tighten
Option retired; option replaced by a new ID (meaning changed); response mode removed; metadata field removed or made required incompatible loosen only with a one-to-one option mapping covering every retired option and no free-text change (Q-34 OPEN, SR-16)
Data type or selection multiplicity (D2-03 recommendation) incompatible never loosen
Parent, entity type, definition owner, repeatability not a version; new identity n/a

Classes. class(1) = {1}; class(n) = class(n−1) ∪ {n} if compatibleWithPrevious(n), else {n}. classSeq(n) is the lowest seq in class(n) and is stored on the version when the class is settled. Compatibility is therefore an equivalence relation: two versions are compatible if and only if they share classSeq, however far apart they are. This is exactly FEAT-001's transitivity rule ("breaking if any skipped version was breaking", docs/features/annotation-versioning/README.md:360-368, RECOVERED) restated as classes (PH-18).

Asymmetry is handled by validity, not by the class. "Options were only added" makes v2 compatible with v1, yet a v2 answer that selects the new option is not valid under v1. The class says the two versions may share a head and be compared; §3.6 says whether a particular answer is valid under a particular version.

How each decision uses it:

Decision Use
SF3, SF5 Answers are shared only within a class (one head per class, §6.1); lineage across classes is shown read-only
AG3 Agreement compares only within a class and flags differing versions inside it (§10.3)
FV3 A completed session satisfies a changed question only when the pinned revision's class matches and the answer is valid (§7.5)
SR-16 autoUpdate is allowed only within a class and only for valid answers (§8.2)
RE4, RE5 A question is held when candidates' pinned revisions span classes (§9.2); prefill only within a class
GS1 Gold whose class differs from the form's current pin is flagged for re-reconciliation (§9.4)

3.6 Per-answer validity

valid(r, v) for a revision r and a question version v of the same QuestionRef holds when:

  1. classSeq(r.questionVersion) = classSeq(v); otherwise validity is not evaluated and the answer's state is NeedsUpdatingVersion (§7.4);
  2. the payload shape matches v's data type and selection multiplicity (true by construction inside a class);
  3. for option answers, every selected optionId is active in v, or an applied mapping (§8.9) has produced a policy-derived successor revision that is valid;
  4. a response mode used by r exists in v, and metadata validate against v's fields;
  5. v's validators accept the value.

Applicability is separate: whether the question applies at all in the session's resolved graph is evaluated by the E23 evaluator and yields NotApplicable, never "invalid". The same evaluator runs on the server for Complete, for Needs updating (VU1), for task validity (RE2) and in AF2 with shared fixtures (E23; FEAT-020's rules file per PH-07).

3.7 System questions

Today system questions are rebuilt from code on every read with fixed GUIDs shared by all projects (Project.cs:225-231, AnnotationQuestion.cs:813-816, CODE-MAIN) and their structure varies by Project.SystemQuestionVersion (AnnotationQuestion.cs:559-592). A snapshot "per code revision" (the package's E24) would make every CAMARADES wording fix a forced FV2 prompt in every canonical project (VA-09).

Rules (D2-06):

  1. System questions are stored as data in a global pmSystemQuestionVersion collection keyed (systemGuid, systemQuestionVersion, seq) with a structuralDigest, seeded idempotently from code at start-up and never rebuilt per read on canonical paths (VB-11). The seed for seq = 1 is today's definition for each systemQuestionVersion that exists in production.
  2. Identity is (systemGuid, systemQuestionVersion) (§3.1), so the v0 and v1 structural variants are distinct identities. definitionOwner = system.
  3. CAMARADES publishes content versions through the designer with the same compatibility declaration as any question (application role; D2-15 for template curation).
  4. Project forms pin a system version like any other. A new system version reaches a project only when its admin publishes a form version that pins it. Deploying code that seeds a new version changes no published form and prompts nobody; the designer shows "newer system version available" and the next publication offers it with the ordinary treatments.
  5. Answers to system questions use the head key with the system QuestionRef; the project ID in the key keeps them project-scoped.

3.8 Lifecycle and in use

Published. A question is published once any of its versions is referenced by a published form or profile version, or once it is adopted from a legacy project (§11). Thereafter (QD1, OWNER):

  • it can be removed from a form by publishing a form version that no longer pins it (its answers stay in history, §8.2);
  • it can be retired (status that refuses new pins in any form or profile version);
  • it can never be deleted, and no code path may delete it: the legacy delete cascade (Project.cs question delete) refuses canonical questions, and the integrity checker (§12.4) treats a missing published definition as a corruption.

Discardable. A committed version that no published form or profile version references, and any pending edit, may be discarded by the designer with an audit entry. A question none of whose versions is published may be deleted (AC-R2a-10's "unpublished draft question").

In use is defined per container because each needs a different test (VA-13):

Container In use when Consequence
Question version any revision pins it compatibility declaration immutable (§3.5)
Question version any published form or profile version pins it not discardable (QD1)
Form or profile requirement version it is published immutable; A-14's "a draft form can change until first use" means an unpublished requirement version. Preview (the AF2 admin host) creates no session
Form or profile requirement version any FormSession (including draft-only), task or gold entry references it reported in usage evidence (PS1); never a mutability question, because published versions are already immutable
Stage settings version it is bound by an Active stage its bindings govern admission (§5.4)

A reviewer session can only exist against a published requirement version, so the "draft-only session against a draft definition" case cannot arise; the research's warning against turning an unedited open into a persistent session is met by creating the session on the first autosave (§7.1).

3.9 Designer pending edits

Designer edits before commit are a per-definition pending record with the same lease, etag and take-over model as reviewer drafts (§7.6), so two admins editing one question get a typed conflict and presence is a hint, never a control (QM v2 D010 harvested; PH-33). No pending-edit trail is kept beyond the current record.

4. Forms

4.1 Requirement versions

An AnnotationFormVersion is (formId, seq), immutable once published, containing:

  • the ordered pins: one (QuestionRef, seq) per question, with every ancestor included automatically (FEAT-001 parent integrity, README.md:244, RECOVERED); at most one version per question identity (ASSUMPTION A-26);
  • per pinned question: required or optional, and for repeatable questions the minimum instance count if any;
  • the validated applicability graph (§4.2) and the renderability result (§4.3);
  • the entity types present, and from O1 the allowed outcome-schema versions (C14);
  • contentDigest, publisher, HLC stamp, and the publication operation that published it (§8).

Publishing a new requirement version is the only way to change any of this (FV1). Removing a question, reordering, changing requiredness or upgrading a pin all create a new version.

4.2 Composition validity

A requirement version is composable only if, for every pinned child version, every conditional-parent reference and every option parent filter resolves to an active option ID in the pinned parent version (VA-06). Today these are validated against the live parent (OptionInfo.cs:51-60, Target.cs:160-194, CODE-MAIN), which is exactly what a version-pinned form cannot rely on. The designer refuses composition with a typed error naming the child, the parent and the missing option; the admin picks a compatible parent version or commits a new child version. The validated graph (nodes = pinned versions; edges = conditions and filters by option ID) is stored in the version and is what the E23 evaluator reads. Dependency cycles are refused as they are for steps.

4.3 Renderability

Canonical forms render on AF2 only; there is no AF1 fallback for canonical routes (VB-08). AF2's stage-review host fails closed on supported categories, exactly one label question per mutable-unit category, parent-closed hierarchies and ordinary roots without targets (src/services/web/CLAUDE.md:353, CODE-MAIN), and its eligibility reads projectDetails.annotationQuestions and stage.annotationQuestions (annotation-form-v2-eligibility.ts:263-274, :295, CODE-MAIN; VB's citation of annotation-form-data-source.ts:25, 748 for the stage-keyed question source is UNVERIFIED here). Therefore:

  1. The AF2 structural guards are part of publication validation, run server-side against the requirement version with fixtures shared with AF2. A version AF2 cannot render is refused at publication with a typed FormVersionNotRenderable error.
  2. AF2 gets a VersionedAnnotationFormDataSource (seam frozen at F1c, built in R2a; E44) that loads the session's pinned form version and its question versions by ID (immutable, cacheable, §12.5), never the current stage's question list.
  3. A canonical route whose version cannot be rendered shows a typed error visible to admins; it never falls back to AF1, which posts legacy submissions that R0 refuses for canonical scopes.

4.4 Operational settings

Form settings are not in the requirement version (VA-07; D2-05). They live on the form head, change by CAS with an append-only audit entry, are read live, and never start an impact flow:

Setting Source
Minimum review target SF2, SF4 (a minimum, so a task is largely target-independent once open)
Optional capacity cap D3-17 (off by default; separate from the target)
Reconciliation compare settings C9
Gold-completeness policy Q-04 (OPEN)
Form-level guidance A-15
Bulk-approve flag Q-11 (OPEN, after R4a)

Sufficiency reads the current target, as SessionCountTarget does today. Changing the target is an audited setting change under the FEAT-024 definition-rewrite fence (C8), not a publication.

4.5 Templates

A template version is (templateId, seq). Instantiation copies question versions into the project (or profile) as new identities at seq = 1, recording copiedFrom = (templateId, seq, QuestionRef). A later template change never changes a copy (DP4, SET1; ownership D2-15).

5. Screening profiles and stage settings

5.1 Profile criteria versions and profile settings

The same split applies (VA-19). A ScreeningProfileVersion is (profileId, seq) and holds the scientific requirement: eligibility question pins with ancestors (profile-owned questions, DP4), decision rules (which answers derive Include or Exclude, DP3), the must-agree supporting-answer set (RX1) and requiredness. It is immutable once published, pinned by decision revisions and profile-owned answer revisions, and published through the Q-26 flow (OPEN; until answered, a profile version can be published only before any decision exists under the profile).

Profile settings live on the profile head with audit history and no impact flow: the exclusion-reason reconciliation toggle (DP5; Off keeps recorded reasons and history), agreement and resolution routes (RX1), rationale settings (Q-32), the Unsure/Maybe and discussion-route options if D4-01 and D4-02 are approved, and the reference to the project's PRISMA phase mapping version (C12; the mapping itself is project-level per DD-20).

5.2 Decision heads and profile classes

A profile criteria version carries compatibleWithPrevious like a question version. Suggestion: decision-rule change or must-agree change that adds a requirement → incompatible; eligibility question changes inherit from the questions' own classes; everything else → compatible.

Decision heads (kind ScreeningDecision) are keyed {project, study, authorScope, profileId} with no question and no class (§6.1). Unlike a shared answer, a decision has exactly one requirement owner (its profile), and two active stages never present different versions of one profile at the same time (§5.4), so the branching that VA-02 found for answers cannot occur. The profile's class is used only to derive a decision's standing under the recorded Q-26 policy (pinnedOlderVersion, needsRenewal) and to decide which cast decisions a collective outcome may read (consistency model §8).

5.3 Stage settings versions and lifecycle

A StageSettingsVersion is (stageId, seq) on the Stage aggregate (brief §1.12) holding bindings (form and profile identities with the version bound and when), steps, dependency edges, route policies, VS1, BL1 and EW1 defaults and the filter element. It references the allocation regime and batch plan by ID; allocation, batch, expiry, target enforcement, in-progress limit, idle timeout and tracking settings are operational and live in their own records with audit (VA-08a; D2-05; D3-18). PV1's "exact stage-settings version" stamp on a revision refers to this requirement-bearing part only.

Lifecycle status is an append-only event list; Active is derived from status; Completed freezes the bindings for display and readiness (RX2, RECOVERED from the v10 prototype lines cited in the ledger) and refuses settings publication except through an approved change request (LC1).

5.4 PV2 binding meaning

PV2 says stage settings bind form and profile versions and historical work pins its requirements, and leaves the transition undecided. Two readings exist (VA-08b): a stage pins a version it presents (so two stages can present different versions of one form to the same session, which contradicts SF1's one session per study and form), or a stage binds the form and records which version it bound and when.

Recommendation (Batch D D2-04): the second. A stage binds the form (or profile) identity and records the bound version and time; the live route always presents the session's resolved version (its pinned version, or the current published version after Upgrade, §7.3); a Completed stage's frozen binding governs only historical display and readiness evaluation for that stage's reports. Consequences: publishing a form version is per form (FV2) and reaches every bound stage; binding a form to a new stage is an admission change for that stage, never a version transition for sessions; claims are keyed by form identity (RT-11).

6. Answers

6.1 Context key

AnswerContextKey (a shared-kernel value object frozen at F1a):

AnswerContextKey
  projectId
  studyId
  authorScope        candidate reviewer ID | reconciled | imported
  definitionOwner    project | profile:<profileId>
  questionRef        {questionId, systemQuestionVersion?}
  classSeq           the compatibility class of the pinned question version (§3.5)
  entityPath[]       instance IDs from the root (§6.5); empty for study-level questions
  populationId       default whole-study population ID derived deterministically from the study ID (DD-24)

classSeq is the change from the package's C2 (VA-02). One head exists per context and class. A revision under a version of a different class creates a new head with the same questionRef and a new classSeq; SF5 shows the older head's revisions as lineage, read-only. "Current", "contains outdated annotations" (SF5, SF6) and Fix are then defined within a class, and the VA-02 scenario (form F on Q v2 with requireReanswer, form G on Q v1 with doNothing) produces two heads that never flag each other.

Consequence to note: when two forms pin different compatible versions of one question, they share a head. An answer changed in F under v2 flags the reviewer's G session "contains outdated annotations" (correct: it is the same answer, changed by the same reviewer), and if the v2 value uses a v2-only option it is NeedsUpdatingValue in G's v1 context. The reviewer chooses whether to Fix (SF5); the flag alone never removes qualification (SF6). The publication dialog lists every other form that pins the question so admins can publish shared questions consistently (U6).

Decision heads omit questionRef, classSeq and entityPath; decision-owned answers add the owning decision's head ID (§6.4). The research's per-kind natural keys (../screening-specialised-annotation-research.md:675-682) are the source of these shapes.

6.2 Key hash and indexes

A unique compound index over entityPath[] is multikey, and MongoDB enforces uniqueness per array element across documents, so heads for (Q, [cohortA, tp1]) and (Q, [cohortA, tp2]) would collide on cohortA (VB-05; documented MongoDB behaviour, not exercised in this repository). Therefore:

  • the head stores the key as an ordered value object and contextKeyHash = SHA-256 over a versioned canonical serialisation (keySchemaVersion included in the hash input);
  • unique indexes are on scalars only: {projectId, contextKeyHash} unique; per kind, partial unique indexes such as {projectId, studyId, authorScope, profileId} where kind = ScreeningDecision;
  • non-unique {projectId, studyId, authorScope, questionId} serves ancestor and SF5 reads, and {projectId, questionId, currentQuestionVersionSeq} serves usage per question version;
  • the head _id is derived deterministically from the hash (§12.3), so a duplicate insert is a DuplicateKey that the command resolves by reload and CAS (consistency model §5).

6.3 Heads and revisions

A head holds the key, kind, state ∈ {Active, Conflicted, Withdrawn}, currentRevisionId (CAS), a denormalised copy of the current payload for reads, and version. A revision holds revisionId (client-proposed, validated, §12.3), headId, seq within the head, questionVersionRef (or a typed AuthoredUnder union for adopted revisions, §11), the typed payload (§3.4), owningParentRevisionRef? and ownedChildRevisionRefs[] (§6.4), PV1 provenance (source stage, step, stage settings version, accepted-answer revision shown), authorship ∈ {reviewer, reconciler, adjudicator, policyDerived, legacySnapshot, imported}, real actor and on-behalf-of, commandId, HLC stamp, recordedAt, observedAt?, contentDigest.

Rules: a revision is never edited; the head's current pointer moves only by CAS inside the command transaction (consistency model §4); a withdrawal is a revision whose payload is the withdrawn marker; "current" is a property of the head, while "pinned" is a property of a session version, and the two differ whenever the reviewer has changed the answer since that session version was made (that difference is the OutdatedOwnAnswer state, §7.4).

6.4 Owner scopes

Two ownership notions share one word in the package (DD-26). They are named apart:

  • definitionOwner (project or profile) is part of the context key and says which requirement owner defines the question (DP4). Two profiles never share answers even with identical wording.
  • owningParent is a revision edge: a decision revision owns its reason revisions; an answer revision may own child answer revisions. Ownership forbids cycles, cross-study edges, a reason owned by two decisions, and a candidate child attached to a reconciled parent (../screening-specialised-annotation-research.md:726-733); the last is a C1 conformance test.

6.5 Entity instances

Nothing in the package said what an instance is or who mints it (VB-09). Today a unit's identity is its label root annotation (AnnotationSession.cs:68-71, CODE-MAIN), AF2 creates units inline with client IDs (src/services/web/CLAUDE.md:347), and delete prunes the unit's answers and outcome cells (:354).

Rules:

  1. Identity is the label head ID in the author's scope, minted once from a client-proposed, server-validated GUID (§12.3). entityPath elements are instance IDs: label head IDs for entity units, instance IDs for repeated-answer branches of a repeatable non-entity question, and option IDs for branches keyed by a multi-select parent's option (the element kinds are listed in the C2 value object, PH-16).
  2. Rename is a new revision on the label head. Identity, answers and cells are untouched.
  3. Delete is a set of withdrawal revisions on the label head and every descendant head of that instance, in one commit. It is append-only; the reviewer's other sessions pinning the instance are flagged OutdatedOwnAnswer on those heads; nothing is pruned. Outcome cells keyed by the instance become inapplicable by derivation.
  4. Duplicate mints new instance IDs; copied revisions carry copiedFrom = sourceRevisionId and the reviewer's authorship.
  5. Population membership is an attribute of the instance; the default population needs no document (DD-24). Outcome cells (O1) key on instance IDs.
  6. Presentation order lives outside versions in a per-session presentation record and never CAS-es the session head (AnnotationSession.cs:49, CODE-MAIN, is today's equivalent).

6.6 Conflicted heads

Legacy duplicates for one context cannot map to one head with a current pointer (VB-13a). A head adopted from conflicting legacy duplicates has state = Conflicted, holds two or more unordered legacySnapshot revisions with a conflictGroup, and no current pointer until the owner resolves it by Fix or Save. While conflicted it is shown with a conflict marker (SF5), excluded from prefill, agreement and autoUpdate satisfaction, and counted as "conflicted" in manifests. Resolution is the reviewer's first explicit revision, which becomes current; the snapshots remain history.

7. Sessions

7.1 Identity and creation

A FormSession is keyed (projectId, studyId, formId, reviewerId) with a deterministic _id (SHA-256 over a versioned canonical key, as CSUUID; VB-16, DD-15), so two tabs cannot create two sessions for one natural key. The document is created by upsert on the natural key at the first autosave, as an explicit Study-free write (consistency model §4); opening a study creates nothing. draft_only is therefore a real state of a real document.

The reconciler's session is not a FormSession: it is an entity of the ReconciliationTask with authorScope = reconciled and a current holder (§9.1), so the natural key never collides with a candidate session even if Q-36 allows self-reconciliation (V2-18).

Screening-only steps have no form. PROPOSAL for F3/F5 (the domain-model revision records it): the session container generalises to ReviewSession keyed (project, study, owner ∈ {form F | profile P}, reviewer); a screening-only step uses the profile-owned session whose versions pin profile criteria versions and decision revisions; a combined step writes a form session version and a profile session version in one transaction under one command ID; drafts follow §7.6 for both.

Claims are separate: canonical claims are keyed by form or profile identity, never by version, and the first explicit Save releases the reviewer's claim inside the canonical transaction (brief §2.1, RT-05, RT-11). Whether a draft holds the reviewer's place is D2-07 (§7.6).

7.2 Session versions and pin maps

A FormSessionVersion is (sessionId, seq) with kind Save, Complete or Withdraw, the pinned form version, the route (stage settings version, step), the accepted-snapshot-available fact and exposure state (C3), commandId, HLC stamp and digest. It pins the complete map of (headId → revisionId) for every answer in the session at that moment (VA-22), the shape FEAT-001's ASV already had (README.md:296-303, RECOVERED). Storage may delta-encode behind the aggregate; the logical contract, previous-version exports, candidate pinning, gold pins and E28's ceiling are all defined on the full map. Each version also stores its resolved question set (the pinned versions applicable in that session version, computed from the pin map and the form version's applicability graph), immutable with the version and recomputable from it (PH-18).

7.3 Transitions

Transition Who Writes Rule
Autosave reviewer draft only never a version, never Study (SL1)
Save reviewer revisions for changed answers, one incomplete session version becomes current (SL3); removes completed qualification; consumes the draft (§7.6)
Complete reviewer as Save, kind Complete validates every required applicable answer under the declared form version (§3.6); one qualifying contribution per reviewer, study and form (SF2)
Fix reviewer, from an outdated flag one incomplete session version pinned to the session's resolved version; opens the form (SF5) explicit; the prior completed version stays history; may be combined with Upgrade
Upgrade reviewer one incomplete session version pinned to the current published form version with the same revision pins offered from the Needs-updating banner, implied by Fix when the reviewer accepts it, or taken on the next Save or Complete that declares the current version; nothing is rebased because pins are unchanged; every Needs-updating mark then shows against the new version (VA-10)
Withdraw reviewer or admin (settled in the C5 ADR) one session version of kind Withdraw append-only; the session no longer counts or qualifies; revisions stay readable; the hard delete reviewers have today never applies
Versioned clear reviewer withdrawal revisions for every head in the session plus a Save replaces "Remove all annotations" (AC-R2a-09)

A Save, Complete or Fix declares the form version it is based on. The server accepts exactly two values: the session's currently pinned version, or the form's current published version (which is the Upgrade). Any other declared version is refused with a typed StaleBase. A Save declaring a superseded version after a publication is accepted pinned to that version and never rebased; the recorded policy applies to it by derivation (§8.8). There is no publication-written transition: the package's "policy transition (creates incomplete versions)" is deleted (VA-03, D2-01).

7.4 Per-answer state

Derived per pinned answer from (pinned revision, head current revision, pinned form version's class for the question, current published form version's class, recorded policy, validity, applicability):

State Meaning Copy (D3-03)
Current Pinned revision is the head's current revision; class matches the required version; valid none
OutdatedOwnAnswer The same reviewer has a newer revision on this head (same class) that this session version does not pin "Outdated answers" / "contains outdated annotations" (SF5)
NeedsUpdatingVersion The required version's class differs from the pinned revision's class under requireReanswer, or the version is incompatible "Needs updating" (VU1)
NeedsUpdatingValue Same class, but the pinned value is invalid under the required version (retired option, tightened validator, removed mode) "Needs updating"
NeedsAnswering Required, applicable and blank, including a newly added required question and a question newly visible after an ancestor change (FEAT-003 "new question" and "newly visible", RECOVERED) "Needs an answer"
PinnedOlderVersion The session is pinned to an older form version under doNothing; no action required "Answered under an earlier version"
NotApplicable Suppressed by an ancestor's answer or mode in this session's resolved graph; the revision is preserved none
Conflicted Legacy duplicates not yet resolved (§6.6) "Conflicting earlier answers"

VA-27 asked for six states; NeedsAnswering and Conflicted are added because FEAT-003 and VB-13 need them. U13 validates all eight.

7.5 Session effective state

Status is the owner's SL3 classification and is a stored fact of the latest explicit version: draft_only, saved_incomplete, completed, withdrawn, plus the separate draft-changes flag. Everything else is derived on read (D2-01):

  • Requirement standing against the current published form version fv_cur, given the latest explicit version E pinned to fv_E and the recorded policies for the publications between them (the latest generation of each, §8.7):
  • fv_E = fv_cur → satisfies if every required applicable answer is Current or OutdatedOwnAnswer, else the session has NeedsAnswering or NeedsUpdating* answers;
  • fv_E < fv_cur → evaluate each question of fv_cur under its treatment (§8.2): unchanged → satisfied by the pinned answer; added → satisfied only under countEarlierCompletes; changedCompatible with autoUpdate → satisfied iff valid; requireReanswer → not satisfied until a revision pinned to the new class exists; doNothing → the session is pinnedOlder, counted or not per the policy's counting choice; removed → ignored.
  • Qualifying = completed ∧ standing ∈ {satisfies, pinnedOlderCounted} ∧ eligible (membership per D4-20; support edit-mode writes excluded from independence only).
  • Flags: hasOutdatedOwnAnswers, hasDraftChanges, informed (exposure, C3, including the NS-06 kind "questioned in reconciliation").

SF6's "if a recorded admin update treatment creates a new incomplete session version" is read as "changes the session's effective standing": under requireReanswer a completed session stops qualifying by derivation, with no version written (D2-01). Canonical readers (admission, qualification, AF2, exports, tasks) derive this on read; query-path projections carry the definition-version vector they were evaluated under and are rewritten by the publication operation (consistency model §8).

7.6 Drafts

Rules only; mechanics are consistency model §4 and §5:

  • One draft record per session, pinned to a base explicit version and base form version, stored as patches against the base, size-capped with E28. It holds a lease (holder = a stable client tab ID shared with tracking's connection model but working with tracking off, RT-10), an etag and a per-holder write sequence; a duplicate write sequence is success; a stale autosave arriving after a newer explicit version is rejected and discarded by the client.
  • A non-holder's edits are kept as a bounded conflict copy (retained N days), so "keeps both" is literal; the second tab is read-only with "Take over editing", which transfers the lease (D2-08).
  • Save and Complete present the draft etag and consume the draft atomically in the commit.
  • No TTL on any draft; removal only by audited discard by the owner, an admin after revocation, or the system with an audit record; stale drafts are visible to admins because they block LC1.
  • No autosave trail beyond the current draft and its conflict copies (PH-33): SL1's "draft changes/history" is satisfied because draft changes are preserved without explicit versions.
  • A cross-form draft: a draft in form G based on an older revision of a head changed through form F gets a typed stale-base conflict at Save that shows both values and keeps G's draft (VB-15).
  • Whether an autosaved draft holds the reviewer's place is D2-07; the recommended middle ground is held while the reviewer is active under today's idle and disconnect timers counting draft activity, released when they lapse with the draft kept, and Complete still allowed afterwards as an extra contribution (SF4's target is a minimum) unless an optional capacity cap applies (D3-17), in which case the reviewer is told honestly and may keep or discard the draft.

8. Publication

8.1 Two-step publication

Committing a question version has no session impact (§3.2). Publishing a form or profile version is the only impact point (FV2, Q-26). Publishing a system question version has no project impact until a project form pins it (§3.7). A publication records a policy and starts an operation; it writes no session versions and no answer revisions (D2-01).

8.2 Treatment vocabulary

The three recovered choices cover changed questions only; FV1's headline case (an added question) and removed questions had no treatment (VA-14). The per-question vocabulary is:

Change class (derived from the diff of fv_prev and fv_new) Treatments Default
added (new pin, required) countEarlierCompletes (earlier Completes satisfy v2 with the question blank; a missing answer is never manufactured, FV3) · requireAnswerBeforeCounting requireAnswerBeforeCounting
added (new pin, optional) no choice; earlier Completes satisfy
removed (pin dropped, or inapplicable through an ancestor change) dropFromRequirement; answers stay in history and remain exportable with their version; children of a removed parent become NotApplicable
changedCompatible (same class, higher version) autoUpdate (the pinned revision satisfies the new version iff valid, §3.6; no revision is written) · requireReanswer · doNothing autoUpdate
changedIncompatible (different class) requireReanswer · doNothing requireReanswer
mapped (incompatible by default, loosened with a Q-34 mapping) autoUpdate applies the mapping (§8.9) · requireReanswer · doNothing requireReanswer until Q-34 is answered
requiredness raised treated as added for the counting choice

autoUpdate is only offered within a class and only satisfies valid answers (SR-16, VA-01); a many-to-one mapping or any free-text question change forces requireReanswer. The per-category part of the policy says which session categories the treatments apply to (completed, saved-incomplete, draft-only) and, for completed sessions left pinned under doNothing, whether they count toward the new requirement (FV3's admin choice; no universal default is asked). The dialog is FEAT-003's four-step flow (scope, compatible changes, incompatible changes, confirmation with per-question and per-category counts; docs/features/question-management/README.md:266-298, RECOVERED) extended with added and removed rows and the system suggestion (U6, PH-18).

8.3 Policy record

IssuePolicyRecord(operationId, generation) (embedded in FormVersionIssue in the domain model, with no collection of its own; the F1a naming ADR confirms the names and shape) holds: the form and the transition (fv_prev versions → fv_new); per question {changeClass, treatment, mappingRef?}; per category {applies, countingChoice}; the admin's rationale (SR-16); the admin, real actor and HLC stamp; the preview digest confirmed; the usage evidence identity read at the fence (projection revision, source revision, digest, or the authoritative-count basis under Q-31(b), MS-04). Generation 1 is written in phase 1; FV4 appends generations (§8.7). The record is append-only and digested.

8.4 Derived effects

Every effect on sessions is the function in §7.5 evaluated against the policy record. Nothing is "applied" to a session. The same evaluator serves admission, qualification, readiness, AF2's banners and exports, so there is one truth and no fan-out. The query-path projections (Study canonical summary, FEAT-024 rows) are rewritten by phase 2 so legacy and pool readers converge; until the sweep reaches a study, admission and readiness for that form pause (D2-10) and fail closed on a stale projection (DC-08).

8.5 Phases

Summary only; the protocol is consistency model §4 and §7:

  • Phase 1 is O(1). Fence the form (Publishing), drain for at least the transaction lifetime plus the expired-transaction sweep plus a margin (D2-10, about 90 s at most), re-check the preview digest (re-confirm with the admin if it changed), then in one short transaction CAS the AnnotationForm head (currentPublishedSeq, publicationSeq), write the policy record (generation 1) and the operation record, and release the fence. No session is enumerated.
  • Phase 2 is an ADR-020-style operation (lease, generation fencing, chunks, predicate pinnedFormVersionSeq < current ∧ appliedPolicyOp < op swept until it matches nothing) that rewrites projections, writes the §8.9 derived revisions, and captures notices once per recipient (C15). Reviewers keep saving throughout; a Save that races the sweep is covered by the predicate.
  • The impact manifest is an audit and preview snapshot built after commit (§8.6).

8.6 Impact manifest

Built after phase 1 from authoritative records at the operation's HLC stamp, chunked, and used for the admin's summary, the notices and PS3 reproducibility; it is never an input to any effect. Its categories are FEAT-003's per-session categories expressed in this model's states (PH-18):

Per session Per question in the session
category (completed, saved_incomplete, draft_only), prior form version, every stage that reaches the session, resulting standing under the policy carriedForward, carriedForwardBlank, autoUpdateSatisfied, needsUpdatingValue, needsUpdatingVersion, needsAnsweringNew, needsAnsweringNewlyVisible, removed, mappedByPolicy, conflicted

Counts and identities come from pmFormSession, pmFormSessionVersion, pmAnnotationHead and pmSessionDraft in one pinned snapshot. draft_only is counted authoritatively from pmSessionDraft (indexed by base form version), never from the materialised usage family (MS-03); the manifest records each count with its basis. The preview shown before phase 1 is the same computation with a digest; phase 1 refuses if the digest moved.

8.7 Revision of a policy

FV4 (OWNER) lets the admin revise an unnecessary update requirement later with history. In this model FV4 appends a policy generation to the same operation; it never rewrites versions, work, drafts or the compatibility declaration (D2-02). The new generation is written by CAS on the policy generation; a phase-2 batch in flight asserts the generation and re-sweeps. Derived states recompute immediately for canonical readers; projections converge through the sweep. Supersession can only loosen or tighten treatments and counting choices within the vocabulary of §8.2; it cannot make autoUpdate available across classes.

8.8 Late Saves and Upgrade

A Save or Complete based on a superseded form version, whether it arrives during the drain, during phase 2 or weeks later, is accepted pinned to the version its client declared and is never rebased (§7.3). The recorded policy applies to it by derivation: under requireReanswer the session is completed but not qualifying until the reviewer Upgrades and Completes; under doNothing it is pinnedOlder and counts per the counting choice. The client shows the typed response ("saved under v1; this form is now on v2") and offers Upgrade. Under doNothing the reviewer may keep working on v1 indefinitely; Upgrade is always available and never forced (VA-10).

8.9 Option mapping as the only derived-revision writer

Q-34 (OPEN) asks whether QM v2's "map answers to updated options" is an approved form of autoUpdate. Until answered, no mapping exists and nothing writes derived revisions. If approved as recommended (explicit per-option mapping, one-to-one, meaning unchanged, old revisions untouched):

  • the mapping {oldOptionId → newOptionId} is declared with the question version as the ground for loosening its compatibility (§3.5) and referenced by the policy record;
  • the phase-2 operation writes, for every head whose pinned revision selects a mapped option, exactly one successor revision with authorship = policyDerived, provenance = {sourceRevisionId, mappingRef, operationId, generation}, the same real-actor fields as the source and the reviewer as effective author, idempotent per (headId, operationId);
  • derived revisions are excluded from SF5 outdated flags and from the "changed since" counts, are included in agreement as the reviewer's answer (meaning unchanged by definition), and are shown with a "mapped on publication" marker and the original value in history;
  • the session's pin map moves to the derived revision on its next explicit version; until then §3.6 rule 3 treats the pinned revision as valid through its derived successor.

This is the single exception to rule 4 in §2 (brief §1.1). An alternative with no writer at all (apply the mapping on read) is recorded in §13; it was not chosen because every reader would need to know every mapping.

8.10 One active publication per form

At most one publication operation per form is active at a time, enforced by a unique partial index on pmFormVersionIssue {formId} where the operation is active, the pattern pmRobRunOperation.ActiveSearch already uses (MongoRobRunStore.cs:67-72, CODE-MAIN). A second publish is refused with a typed PublicationInProgress (D2-11). FV4 is not a new publication; it is a generation on the existing operation (§8.7). The same rule applies per profile.

9. Reconciliation and gold under versioning

9.1 Task identity and pins

The package keyed the task by study × form × version-compatibility class (a round-1 resolution of B-28). That is wrong at form grain, because compatibility is per question, and it contradicts RE4's one task per study and form (VA-04, DD-07). Correction: the task key is (projectId, studyId, formId) with a deterministic _id. The task pins, as state, an input set (taskId, seq) holding the form version it reconciles against and the full set of qualifying candidate session versions (SF4: all of them). A new input set is appended when inputs change (§9.3); the gold snapshot records which input set produced it. The reconciler's session is an entity of the task with authorScope = reconciled, a current holder (X-RECLAIM, brief §2.1) and drafts keyed by the task.

9.2 Held questions

Per question of the task's form version, held is derived when the candidates' pinned revisions for that question fall in different classes, or when a candidate's pinned value is invalid under the task's form version, or when a candidate head is Conflicted. A held question blocks only itself (prefill, agreement, the question's own final acceptance); the rest of the form reconciles. The workspace shows the v10 held banner (/home/chris/.codex/visualizations/2026/09/23/01a0cbec-0c32-7103-ab5a-bfc05665deb7/syrf-v10-review-2026-10-02/source/design_handoff_syrf_v10/RECONCILIATION.md:47-50) with "Ask vN reviewers to update", which raises a Needs-updating request on those sessions (a C15 notice; the session's standing is unchanged by the request). v10's "Mark compatible" (RD12) is replaced by the admin's compatibility declaration, which is immutable once pinned (D2-02); a held question is released by the candidates' Upgrade, never by relabelling.

9.3 Drift triggers

A task moves to "inputs changed · re-check", never to retraction of gold, when: a new qualifying candidate appears; a candidate Saves after Complete; a candidate Fixes or Upgrades after gold; the form's current version changes by publication; a publication policy de-qualifies a pinned candidate (requireReanswer, VA-11b); a candidate head becomes withdrawn; a dedup merge or split aliases the study. Under doNothing with counting, candidates keep counting until they submit, which is v10's "their last completed review keeps counting" (RECONCILIATION.md:49); under requireReanswer they stop qualifying by derivation. The admin's publication choice selects which; the task never guesses.

9.4 Gold snapshots

A GoldSnapshot is (studyId, seq); entries are (question context without author, reconciled revisionId); a new snapshot keeps unchanged references (GS1). Each reconciled revision pins a question version, so each entry has a class. Derived per entry: goldNeedsReReconciliation = classSeq(current form pin of q) ≠ classSeq(entry's revision) ∨ ¬valid(entry's revision, current form pin) (VA-11a). Gold stays effective while flagged (QY1's analogue), exports and PRISMA manifests label every gold value with its question version and class (§10.2), and the task shows the flag; re-reconciliation produces a new snapshot. The pointer on StudyGold moves by CAS (consistency model §4).

9.5 Shared question gold

Overlapping forms share question gold (RE4, OWNER). The package proposed "first publisher wins, challenge only by query". Batch D D2-09 asks which: the recommended alternative is that the second task sees existing shared gold prefilled as accepted, with its source snapshot and reconciler shown, and the second reconciler may revise it in their own final submission, producing a new snapshot with provenance {supersedesEntry, task, reconciler}; queries remain the route for everyone else. Until D2-09 is answered, pilots use forms that do not overlap on reconciled questions (A-19 holds until R2d anyway).

9.6 Queries

The query target is the reconciled revision ID (VA-23), so "one work item per accepted-answer version" (QY2) is one work item per revision, shared unchanged across any number of snapshots that pin it. QY9's "current applicability" compares the target revision with the snapshot's current revision for that head and with the form's current class for the question; a concern on a revision that gold no longer pins, or whose class is no longer current, is flagged for the assigned reviewer and never retargeted silently.

9.7 Screening adjudication

Adjudications are revisions on the reconciled-authority ScreeningDecision head, so they are versioned by construction (V2-18); the ProfileAdjudication record in the domain model is the command record with a generation, not a second store of the decision. Following Chris's 25 September clarification, a submitted replacement of an input decision makes the earlier adjudication inapplicable to the new input vector while keeping its history (../screening-specialised-annotation-research.md:818-823, RECOVERED); the outcome projection derives this (consistency model §8).

10. Statistics, agreement, exports and PRISMA with mixed versions

10.1 Usage

  • Question-version usage is counted from revisions: distinct (studyId, authorScope) with a revision pinned to (QuestionRef, seq), read from pmAnnotationHead and pmAnnotationRevision.
  • Form-version usage is counted from session versions: the latest explicit version per session by category, deduplicated across stages (one session per study, form and reviewer, so there is nothing to sum).
  • draft_only is counted from pmSessionDraft by base form version, authoritatively, inside the publish fence (MS-03); it is never point-maintained by FEAT-024.
  • The materialised usage families (FEAT-024 amendment at F2 and F5, MS-02) are a swap-in behind the same interface; Q-31(b) authoritative counting at the protected boundary is the first pilot path.

10.2 Exports and manifests

  • Every exported answer carries (questionRef, questionVersionSeq, classSeq, optionId[], value[], responseMode?, answeredUnderVersion, qualificationPolicy); gold values carry the snapshot seq and goldNeedsReReconciliation (VA-17, SR-16).
  • Wide exports (one column per question) are generated per form version or per class, with a per-cell version column; a column never mixes classes.
  • Suppressed answers are omitted or carry an explicit status; a preserved inactive value is never emitted as live (PH-06).
  • Manifests list every definition version and its digest, the policy generations in force, the watermark, coverage per dataset and, for adopted data, AuthoredUnder (§11).
  • Extraction exports default to collectively Included studies (SR-17, methodology coverage) with explicit options for the rest.

10.3 Agreement

  • Agreement compares only within a class, flags differing versions inside a class (AG3), never compares across classes, and treats Conflicted, Unknown-authored (§11) and held answers as "not comparable" with explicit counts.
  • Multi-select agreement needs identical option-ID sets; overlap is shown separately (AG2).
  • Independent versus informed follows exposure (VS2): an accepted revision rendered into view, and the NS-06 kind "questioned in reconciliation", make later versions of that reviewer's session on that study × form informed. The agreement store is its own rebuildable store (D3-11).
  • Statistical method and denominators are Q-16 and Q-04 (OPEN).

10.4 PRISMA

PRISMA reads the collective authoritative outcome (PR1) from the outcome projection with its input-version vector; it never reads FEAT-024 rows (MS-11) and never changes because a form version, target or extra assessment changed. Report snapshots freeze the definition versions they were built from.

10.5 Transaction time only

As-of means "what SyRF knew at that commit stamp", not "what was true then" (VA-21). Order comes from per-aggregate sequences and the HLC stamp; as-of(T) is offered only for T older than the watermark (consistency model §11). observedAt on a revision and legacy DateTimeCreated (settable, stamped at construction) are evidence fields with trust levels and are never used for ordering or as-of selection.

11. Legacy adoption as v1

Rules for the R6 domain mapping (migration §3), which this document corrects where VB-13 showed it could not be applied:

Legacy record Canonical result Rule
Project question Identity (questionId kept, definitionOwner = project, entityTypeId from the category alias, parent kept) plus content version seq = 1, classSeq = 1, published Counts as published (QD1). Ancestors added to a form at adoption are display-only and never required (VB-13c)
Options v0: optionId = OptionInfo.Id (OptionInfo.cs:114-131, CODE-MAIN); v1: IDs minted per value Unmatched values listed in the manifest with a disposition
Conditions and parent filters v0 _v0OptionId → optionId; v1 and ADR-011 hybrid value sets → option IDs by value; boolean conditions kept Any value with no option is a manifest exception; the form version is not composable until resolved
System questions Pin (systemGuid, Project.SystemQuestionVersion, seq 1) No structure is inferred from the current code; the seed for that systemQuestionVersion is the pinned definition
Stage question set One initial form requirement version per stage set, bound to that stage; merging identical sets is an admin-reviewed choice Never automatic
Answer Head with the legacy annotation ID preserved through LegacyIdAlias, one legacySnapshot revision, AuthoredUnder typed union: Verified(v1) when Annotation.Question (Annotation.cs:41, AnnotationOptions.cs:30, CODE-MAIN) equals the adopted v1 wording, else Unknown (VA-18) Verified by comparison because the legacy upsert validates placement only for new questions (Project.cs:501-510, CODE-MAIN) and locks nothing, so wording can change after answers. Unknown revisions are excluded from same-version agreement and from exact-match prefill; they remain candidates with a coverage label. Option answers map by value to option IDs; an unmapped value is kept verbatim in an unmappedLegacyValue payload and is invalid under v1
Conflicting duplicates for one context One Conflicted head (§6.6) Never "latest wins"
Session One current-only session version per legacy session with a full pin map of the adopted revisions; legacy-completed, unvalidated with the admin's count choice (E10); nullable timestamps kept Prior Save or Complete versions are never fabricated
Reconciled answers LegacyAuthorityUnknown snapshot, never a gold snapshot (Q-35)
Everything with a legacy ID LegacyIdAlias {projectId, legacyKind, legacyId, canonicalKind, canonicalId, manifestId}, unique on (projectId, legacyKind, legacyId) Used by #3944/#3945 remapping and exports (VB-13e); the alias table is itself append-only

12. Storage and enforcement summary

Physical choices are the F1a storage ADR's (E15); this section fixes the rules the ADR must meet and the blueprint it starts from (VB improvement 1). Transactions, retries and the cache rule are consistency model §4 and §5.

12.1 Collections

Collection names are explicit and decoupled from class names (today they derive from the class name, MongoContext.cs:154-163, CODE-MAIN, which is why QM v2's AnnotationQuestionV2 would land in pmAnnotationQuestionV2); a test asserts the map. Names below are PROPOSAL for the F1a naming ADR. Enums are stored as strings parsed from a closed set.

Collection Identity and unique keys Other indexes Notes
pmQuestionDefinition _id record GUID; {projectId, questionId, systemQuestionVersion} unique {projectId, status} identity and status only
pmQuestionDefinitionVersion {definitionId, seq} unique {projectId, questionId, classSeq} immutable; digest
pmSystemQuestionVersion {systemGuid, systemQuestionVersion, seq} unique global; seeded idempotently
pmEntityType _id stable system IDs minted at F1a; {projectId, name} unique for project types
pmAnnotationForm {projectId, formId} unique; head holds currentPublishedSeq, publicationSeq, settings, version phase-1 CAS target
pmAnnotationFormVersion {formId, seq} unique immutable; applicability graph; renderability result
pmDefinitionSettingsAudit append-only {ownerRef, seq} form, profile and stage operational settings changes
pmScreeningProfile, pmScreeningProfileVersion as for forms
pmStageSettingsVersion {stageId, seq} unique on the Stage aggregate per brief §1.12
pmFormSession deterministic _id; {projectId, studyId, formId, reviewerId} unique {projectId, formId, currentFormVersionSeq, status}; {projectId, studyId, reviewerId} publication predicate, usage, SF5 reads, candidates
pmFormSessionVersion {sessionId, seq} unique; {projectId, commandId} unique (the command ledger entry) {projectId, formId, commitStamp} full pin map; resolved question set
pmSessionDraft {sessionId} unique {projectId, formId, baseFormVersionSeq} conflict copies embedded, bounded
pmSessionPresentation {sessionId} unique entity order and similar; never CAS-es the session
pmAnnotationHead deterministic _id; {projectId, contextKeyHash} unique; partial unique per kind {projectId, studyId, authorScope, questionId}; {projectId, questionId, currentQuestionVersionSeq} current payload copy
pmAnnotationRevision _id client-proposed validated; {headId, seq} unique; {projectId, commandId, headId} unique {projectId, studyId, commitStamp} as-of reconstruction
pmFormVersionIssue, pmFormVersionIssueChunk one active per form (unique partial index); chunks {operationId, chunk} unique phase 2; manifest chunks; IssuePolicyRecord generations embedded (generation unique within the issue; FV4), no collection of their own; the F1a naming ADR confirms the shape
pmReconciliationTask deterministic _id; {projectId, studyId, formId} unique input sets embedded or {taskId, seq}
pmStudyGold, pmGoldSnapshot {studyId} unique; {studyId, seq} unique pointer CAS
pmQueryWorkItem {projectId, reconciledRevisionId} unique R4b
pmLegacyIdAlias {projectId, legacyKind, legacyId} unique {canonicalId} append-only

Constraints the ADR must keep: revisions are never embedded in sessions (SF3 and SF5 share them; gold and tasks reference them); the full pin map lives in its own document per session version; revisions stay in their own collection for the as-of index; Study holds only the canonical summary (consistency model §8); every new pmStudy index goes through the operator route (VB-18).

12.2 Append-only enforcement

The generic save is an upsert ReplaceOne filtered on Audit.Version (MongoExtensions.cs:262-290, CODE-MAIN), so re-saving a loaded immutable record silently replaces it (VB-10). Therefore: an AppendOnlyRecord base and an IAppendOnlyRepository<T> exposing only Insert, InsertMany and Find; on DuplicateKey the repository compares digests and returns the existing record idempotently or throws a typed conflict; an architecture test in the pattern of StudyWriteLockArchitectureTests fails the build on ReplaceOne, Update*, Delete*, FindOneAnd* or update models in bulk writes against a registered immutable collection; no TTL index on any canonical collection; mutable heads (form, profile, session, head, task, gold pointer) are written only through non-upsert CAS saves (#3985 prerequisite).

12.3 Identifiers and digests

  • Deterministic IDs (SHA-256 over a versioned canonical key, stored as CSUUID, the #3944 precedent) for aggregates with natural keys: FormSession, AnnotationHead (from the key hash), ReconciliationTask, StudyGold, the default population, CanonicalOwnership. The natural-key unique index remains the real guard; a DuplicateKey means "reload and CAS".
  • Client-proposed, server-validated IDs for revisions and entity instances: well-formed, unused, same project, generated per command; AF2 keeps its optimistic client IDs and never remaps temporary IDs across the store, comments, order and outcome cells (VB-16).
  • Legacy IDs are kept at adoption through LegacyIdAlias (§11).
  • Content digests (SHA-256 over a versioned canonical serialisation) on every definition version, revision, session version, policy record and snapshot; recorded in export manifests, so "two exports at the same watermark are identical" is a checksum comparison.

12.4 Referential invariants and the integrity checker

ID Invariant
I1 Every revision references an existing question version (or system version) of the same QuestionRef as its head, in the head's class, with a payload shape the version defines; a policy-derived revision references an existing source revision, mapping and operation
I2 Every session version references an existing published form version and existing revisions; every pinned head belongs to the session's project, study and author; (projectId, commandId) is unique
I3 Every gold snapshot entry references an existing revision with authorScope = reconciled on the same study; StudyGold.currentSnapshotSeq exists; every query references a reconciled revision
I4 Every stage settings version references existing published form and profile versions in the same project; every form version's pins reference existing question versions whose applicability graph resolves (§4.2)
I5 Every task input set references existing candidate session versions on its study and form; every held question references an existing pin
I6 No cross-project reference except system versions; every LegacyIdAlias target exists; every head's classSeq equals the class of its revisions' versions; no published definition is missing

A read-only integrity checker ships in R2a, runs on seeds in CI, in E31 restore rehearsals, in R6 verification and after every restore; it reports zero findings on seeds and detects an injected dangling reference per invariant. An architecture test lists every pm* collection with a projectId against the deletion-lifecycle and restore registries (VB-11d).

12.5 Caching

Immutable definitions (question, system, form, profile and stage settings versions) are cached process-wide by (versionId, digest) with no invalidation, and version-by-ID endpoints return Cache-Control: immutable, which keeps AF2 history and Needs-updating reads off the Project document (118–465 KB typical per .claude/rules/repository-cache.md). Canonical commands never take deciding reads from the shared RepositoryCache (consistency model §4).

12.6 QM v2 harvest and avoid for versioning

Harvest (adapted to this model) Avoid
VersionHistory<T>; the typed AnnotationAnswer payload with EnsureCompatible (as the §3.6 validity check); ChildQuestionScope as the repeatable identity property; CandidateProjectQuestionSetValidator, CrossQuestionValidationService and AnnotationValidationState as E23 inputs; SystemQuestionFactory as the §3.7 seed builder; AnnotationMutationMapper, ExtractedAnnotationLegacyMapper, MigratedStudyReadModelAssembler and MigrationValidationService as R6 adapters and parity checks; the ExportSpec mode reservation renamed to form-version selectors ReconstructiveRollbackService and RevertToEmbeddedQuestionModel (destructive down-migrations); drafts and question-set versions inside the Project document; AQVersion.PublishDecisions, Optional and Multiple placement (requiredness belongs to the form; multiplicity is content but always incompatible); untyped AnswerOptionFilters; unbounded version arrays embedded in annotation and session documents; annotation identity without entity path, owner scope or population; stage-keyed export selectors; raw AsOfDate; ReplacementDraftLineagePlanner unless D2-03 keeps D38

13. Alternatives considered

Alternative Why not
Event sourcing (commands as the store, state by replay) Commands already produce receipts and a total order (the command ledger plus HLC); nothing needs replay; projections here are rebuilt from immutable revisions and versions, not from events; the repository and team have no event-store tooling. The model keeps what event sourcing gives (append-only, derived projections) without a second write model
Bitemporal records (valid time plus transaction time) Valid time is provenance only (observedAt, trust levels); no reader needs "what was true then" as a query dimension; as-of by transaction time plus coverage labels satisfies EX1 and EX2 (VA-21)
Copy-on-write session documents (a full session document per version) Would embed revisions in sessions, which SF3 and SF5 forbid (shared revisions across forms) and which gold and tasks reference; document growth and the as-of index both suffer
Per-form snapshots with content-hash equality (freeze the whole form per publication; equal hash means compatible) SF3 and DP4 need question-level identity and compatibility; hash equality is fragile under presentation edits and silent under meaning changes; the per-question class gives the same immutability with meaning preserved
Materialised policy-created session versions (DD-10's alternative: phase 2 writes real versions with provenance = policy) Contradicts SL3 (a version no reviewer made becomes current), makes the publish operation an author of evidence on shared heads, makes FV4 resurrect or supersede them, and costs one version per affected session; derived standing gives the same answers at O(1) (brief §1.1, D2-01)
Apply option mappings on read (no derived revisions at all) Simpler in storage, but every reader (exports, agreement, AF2, PRISMA) would have to know every mapping ever recorded; a single idempotent derived revision keeps readers ignorant of mappings (§8.9)
Class per form (the package's task key) Compatibility is per question; a form version that changes five questions would split one study × form into several tasks against RE4 (§9.1)
Per-question versions with classes (chosen) Transaction-time revision log with derived projections; satisfies every owner decision; fits MongoDB as SyRF uses it (append-only inserts, partial unique indexes, deterministic IDs, short transactions on a few documents)

14. Conformance fixtures

Fixtures are versioned data run by the C2, C4, C5 and C9 suites at F1a and F4; the acceptance drafter maps them to criteria. IDs are provisional.

ID Fixture
FX-VM-01 Compatible added option: the answer is shared by two forms with no flag
FX-VM-02 A v2-only option is never offered under v1; a prior v2 value is rendered read-only with v2's labels by the Needs-updating presenter
FX-VM-03 Two forms on incompatible versions of one question never flag each other (two heads)
FX-VM-04 Fix shows the in-class current revision
FX-VM-05 A late Save declaring v1 after v2 is published is accepted pinned to v1 and evaluated under the recorded policy; a Save declaring any other version is refused StaleBase
FX-VM-06 Upgrade keeps every pin, shows Needs-updating marks against v2, rebases nothing
FX-VM-07 Publishing a form with 10,000 sessions writes no session versions and no revisions; phase-1 time is flat across 1k, 10k and 100k sessions
FX-VM-08 FV4 appends a policy generation; qualification is restored without touching versions or work; a batch in flight asserts the generation
FX-VM-09 Renaming an option's value keeps answers valid; retiring it makes them NeedsUpdatingValue
FX-VM-10 Composing a form that pins a child condition on an option absent from the pinned parent version is refused, naming the child, parent and option
FX-VM-11 An incompatible publication flags affected gold entries for re-reconciliation; gold stays effective and is labelled with its version
FX-VM-12 One task per study × form; one held question when candidates span classes; the rest reconciles; "Ask vN reviewers to update" raises a request and changes no standing
FX-VM-13 A wide export with mixed versions carries per-cell version, class and option IDs and never mixes classes in a column
FX-VM-14 Adopted answers are Verified(v1) when Annotation.Question equals the v1 wording, otherwise Unknown; Unknown is excluded from same-version agreement and prefill
FX-VM-15 Three-version chain v1→v2 compatible, v2→v3 incompatible: classSeq(v1)=classSeq(v2)=1, classSeq(v3)=3; agreement v1/v2 flagged; v1/v3 never compared
FX-VM-16 Flipping a compatibility flag after a revision pins the version is refused; flipping before that recomputes classes and re-validates active policies
FX-VM-17 A data-type change creates an incompatible version of the same identity (pending D2-03); old revisions remain readable; a new head is created on first answer
FX-VM-18 Heads differing only in the second entityPath element coexist; an exact duplicate is refused; the hash is stable across key schema versions
FX-VM-19 Conflicting legacy duplicates adopt as a Conflicted head with no current pointer; excluded from prefill and agreement; resolved by the owner's Fix
FX-VM-20 LegacyIdAlias remaps a #3944 thread reference and an export reference
FX-VM-21 Unit delete is a withdrawal commit that flags the reviewer's other forms; rename keeps identity; duplicate mints new IDs with copiedFrom
FX-VM-22 Suppressed descendants survive Save and Complete; exports resolve suppression
FX-VM-23 Added required question: countEarlierCompletes keeps earlier Completes qualifying; requireAnswerBeforeCounting does not; no answer is manufactured
FX-VM-24 Removed question: answers stay in history and exports; children of a removed parent are NotApplicable
FX-VM-25 Deploying a new system-question version changes no published form and prompts no admin; the next form publication offers it
FX-VM-26 v0 and v1 system error-type variants are distinct identities with distinct pins
FX-VM-27 A session pinned to v1 renders v1 after v2 is published; a form AF2 cannot render is refused at publication; a canonical route never falls back to AF1
FX-VM-28 The append-only architecture test is green; digests verify on read-back; the collection-name map test passes; no TTL index exists on canonical collections
FX-VM-29 The integrity checker reports zero findings on seeds and detects one injected violation per invariant I1 to I6
FX-VM-30 A previous-version export of a session equals its full pinned map
FX-VM-31 All eight per-answer states render and are explained (U13)
FX-VM-32 Two tabs: conflict copy kept; take-over transfers the lease; a stale autosave is rejected; a duplicate write sequence succeeds; Save consumes the draft atomically
FX-VM-33 A draft in form G based on an older revision changed through form F gets a typed stale-base conflict showing both values and keeps G's draft
FX-VM-34 A policy-derived mapping revision (pending Q-34) has provenance, is excluded from outdated flags, is written once per head and operation, and re-running the sweep writes nothing
FX-VM-35 Question-version usage counts revisions; form-version usage counts session versions; draft_only counts pmSessionDraft; a shared session counts once across stages
FX-VM-36 Agreement never crosses a class, flags differing versions within one, and separates informed contributions including "questioned in reconciliation"
FX-VM-37 Two tabs' first autosaves create one FormSession; a client-proposed revision ID already used in another project is refused
FX-VM-38 A second publish on a form with an active operation is refused PublicationInProgress; FV4 during phase 2 CAS-es the generation
FX-VM-39 doNothing with and without counting yields pinnedOlderCounted and pinnedOlderNotCounted; the per-answer state is PinnedOlderVersion
FX-VM-40 A profile criteria version with a rule change is incompatible; cast decisions take the standing the Q-26 policy records (parameterised until Q-26 is answered)
FX-VM-41 Publishing a stage settings version changes no session pin and no qualification; a Completed stage's display is frozen (pending D2-04)
FX-VM-42 Changing a target, compare setting, DP5 or route creates no version, no impact flow, and one audit entry
FX-VM-43 The legacy category string resolves to the stable entity-type ID; enabling C1 changes no identity
FX-VM-44 A query targets a reconciled revision ID; after a new snapshot that no longer pins it, or a class change, the concern is flagged for current-applicability review and not retargeted
FX-VM-45 Shared gold in a second task is prefilled as accepted; a revision produces a new snapshot with provenance (parameterised until D2-09 is answered)

15. Decisions needed and engineering items

15.1 Decisions for Chris

All cited from the resolution brief's Batch D; none is minted here.

ID What this document assumes until answered Recommendation in the brief
D2-01 Effects derived; projections rewritten by an operation; SF6 read as "changes the effective standing" yes
D2-02 Compatibility declared at commit, immutable once pinned; FV4 never changes it yes
D2-03 Data type and multiplicity edits refused on published questions (today's behaviour) incompatible version of the same identity
D2-04 A stage binds the form identity; the live route presents the session's resolved version yes
D2-05 Target, compare settings, gold completeness, guidance, DP5, routes, allocation, batches, expiry outside requirement versions yes
D2-06 System questions as data; adoption only through a form publication yes
D2-07 Draft and the reviewer's place: the recommended middle ground (§7.6) middle ground
D2-08 Second tab read-only with take-over; conflict copy yes
D2-09 Pilots avoid overlapping reconciled questions until answered second task may revise
D2-10 Scoped pauses with limits; drafts kept yes
D2-11 One active publication per form yes
D2-16 Form size ceiling in pins and bytes; the 2,023-question project stays legacy (VB-12's figure; UNVERIFIED here) yes
D3-17 Optional capacity cap as a form setting separate from the target yes
D4-17 FEAT-001 D54/D55 replaced by RE2's non-blocking warning; no enforcement levels yes
D4-04 Training rounds as a step kind that never versions evidence as a contribution (PH-33's disposition) yes

OPEN questions this model depends on: Q-34 (no mapping and no derived writer until answered), Q-26 (profile versions publishable only before any decision), Q-04 (gold completeness follows requiredness), Q-29 (target-1 forms create no task), Q-35 (legacy reconciled answers never gold), Q-36 (no self-reconciliation by default), Q-16 (agreement method).

15.2 Engineering items E36 to E45

ID What Owner lane and contract Freeze gate
E36 Compatibility and class evaluator: diff classifier producing the system suggestion (§3.5 table), class derivation and classSeq stamping, the immutability guard (refuse a flip once pinned; re-validate active policies before that), and the designer pending-edit record with lease and etag (§3.9) L2 / C4 F1a
E37 Option identity and payload contract: optionId minting, the typed payload (value XOR mode, metadata, option IDs), display value and label resolution, and the v0/v1 legacy mapping with the manifest of unmatched values (§3.3, §11) L2 and L1 / C1, C4 F1a (payload); R6 (mapping)
E38 Composition and renderability validator: applicability graph against pinned parent versions, AF2 structural guards server-side with shared fixtures, typed refusals naming the question (§4.2, §4.3) L2 and L5 / C4 F1a
E39 System question store: idempotent seeding into pmSystemQuestionVersion, (systemGuid, systemQuestionVersion) identity, the CAMARADES publication command, no per-read rebuild on canonical paths (§3.7) L2 / C4 F1a
E40 Session effective-state evaluator: per-answer state enum, requirement standing, qualification, policy composition across publications and FV4 generations, one implementation used by admission, readiness, AF2 and exports (§7.4, §7.5, §8.4) L1 and L7 / C5 F1a design; F2
E41 Head key value object, canonical serialisation and hash, partial unique indexes per kind, Conflicted state, AuthoredUnder union and LegacyIdAlias (§6.1, §6.2, §6.6, §11) L1 and L15 / C2 F1a; R6 for the alias
E42 Entity instance commands (create, rename, withdraw, duplicate), population membership as an instance attribute, outcome cells keyed by instance, presentation record outside versions (§6.5) L1 / C2, C13 F1a
E43 Append-only persistence: AppendOnlyRecord, IAppendOnlyRepository<T>, explicit collection-name map with its test, content digests, the architecture test, no-TTL rule, string enums, and the integrity checker (§12.2 to §12.4) L1 / C1 F1a; checker in R2a
E44 AF2 VersionedAnnotationFormDataSource, the Needs-updating presenter contract (fromVersion, toVersion, treatment, reason, guidance, prior value rendered with fromVersion's labels), immutable-definition cache with Cache-Control: immutable, typed no-fallback error (§4.3, §12.5) L5 / C17 F1c (seam), R2a
E45 Reconciliation under versioning: task input-set versions, per-question held derivation, gold re-reconciliation derivation, second-task revision of shared gold (D2-09), query target identity (§9) L6 / C9 F4

15.3 Assumptions

ID Assumption Basis Cost if wrong
A-25 Question versions form one linear sequence per identity (seq 1..n); branches never exist; a copy from a template or another profile is a new identity at seq 1 FEAT-001's sequential VersionNumber; DP4 copies Class derivation and classSeq need DAG rules; the designer needs merge semantics
A-26 A requirement version pins at most one version of each question identity; two forms may pin different versions of one question only across forms, never within one FEAT-001 QSV: one AQVersionRef per question Composition, the pin map and the head key need a per-pin version dimension

Resolution record

Finding Category Where Note
VA-01 Adopted §3.5, §3.6 One definition: declared at commit, immutable once pinned, classes as an equivalence relation with classSeq; validity separate; per-decision usage table; D2-02
VA-02 Adopted §6.1 classSeq in the head key; one head per context and class; the shared-compatible-version consequence stated
VA-03 Corrected §7.3, §7.5, §8.1, §8.4, §8.9 Derived model chosen; "policy transition" deleted from C5; mapping is the only derived writer; D2-01
VA-04 Corrected §9.1, §9.2 Task key study × form (RE4 already decides); held per question
VA-05 Adopted §3.3, §11 Stable optionId; answers store IDs; rename compatible, retire incompatible; adoption reuses OptionInfo.Id
VA-06 Adopted §4.2 Composition validity against pinned parent versions; graph stored
VA-07 Question §4.4 Requirement version versus form settings; D2-05
VA-08 Question §5.3, §5.4 (a) allocation, batches, expiry outside the stage settings version (D2-05); (b) binding meaning D2-04
VA-09 Question §3.7 System questions as data, (guid, SystemQuestionVersion) identity, opt-in adoption; D2-06
VA-10 Adopted §7.3, §8.8 Upgrade transition; late Save pinned to the declared version
VA-11 Adopted; © Question §9.3, §9.4, §9.5 (a) gold re-reconciliation derived; (b) publication de-qualification joins drift triggers; © D2-09
VA-12 Adopted §7.6 One draft record with conflict copies (brief §1.8); D2-08 for the UX
VA-13 Adopted §3.8 "In use" defined per container; published versions immutable regardless of sessions
VA-14 Adopted §8.2 added, removed, changedCompatible, changedIncompatible, mapped with treatments and defaults
VA-15 Adopted §3.8 Published, retired, removable from forms, discardable when unreferenced
VA-16 Question §3.1 Both options presented; recommendation incompatible version; D2-03
VA-17 Adopted §10.1, §10.2 Per-cell version, class and option IDs; wide exports per form version or class; usage from revisions versus session versions
VA-18 Adopted §11 Verified(v1) versus Unknown from Annotation.Question; code verified
VA-19 Adopted §5.1 Profile criteria version versus profile settings; Q-26 applies to criteria versions only
VA-20 Adopted §2 table, §9.7 Outcome is a rebuildable projection with its vector; mechanics in the consistency model
VA-21 Adopted §10.5 Transaction time only; observedAt and legacy timestamps never order
VA-22 Adopted §7.2 Full pin map per session version; storage may delta-encode
VA-23 Adopted §9.6 Query target = reconciled revision ID; QY9 comparison defined
VA-24, VA-26 Noted — No findings with these IDs exist in the verbatim VA report
VA-25 Adopted §3.1 Entity-type ID is the structural property; category string is a display alias
VA-27 Adopted §7.4 Eight per-answer states (six asked plus NeedsAnswering and Conflicted); session standing in §7.5
VA improvement 1 Adopted §2 The rulebook table
VA improvement 2 Adopted whole document The simplest model that satisfies the ledger, with D2-03 as the identity choice
VA improvement 3 Adopted §8.1 Two-step publication stated once
VA improvement 4 Adopted §7.5, §8.4, §9.2, §9.4 Derive rather than store; PRISMA phase mapping stays project-level (DD-20), referenced from profile settings
VA improvement 5 Adopted §14 Fixture set, extended
VA improvement 6 Adopted §11 Wording evidence and OptionInfo.Id reuse
VA improvement 7 Adopted §8.2 U6 shows the per-question vocabulary and suggestion
VA question 1 Question §3.5 D2-02
VA question 2 Question §3.1 D2-03
VA question 3 Question §8.4, §8.9 D2-01
VA question 4 Question §9.5 D2-09
VA question 5 Question §5.4 D2-04
VA question 6 Question §3.7 D2-06
VA question 7 Question §4.4, §5.1, §5.3 D2-05
VB-05 Adopted §6.2 Key hash, scalar unique indexes, partial per kind; fixture FX-VM-18
VB-06 Adopted §8.5, §8.8, §8.10 Versioning rules stated (late Save pin, one active publication, FV4 generation CAS); protocol in the consistency model §4 and §7
VB-08 Adopted §4.3, §12.5 Versioned data source, Needs-updating presenter, renderability at publication, no AF1 fallback; one VB citation UNVERIFIED
VB-09 Adopted §6.5 Instance identity, rename, delete as withdrawal, duplicate with provenance, population attribute
VB-10 Adopted §12.1, §12.2, §12.3 Append-only repository and test, explicit names, digests, no TTL
VB-11 Adopted §3.7, §12.4 Record GUID _id with unique natural key; global system store; I1 to I6; checker in R2a
VB-13 Adopted §6.6, §11 Conflicted head, AuthoredUnder union, display-only ancestors, v0/v1 mapping, LegacyIdAlias
VB-15 Adopted §7.6 No TTL, audited discard, patches with E28 cap, cross-form draft conflict fixture
VB-16 Adopted §12.3 Deterministic IDs for natural keys; client-proposed validated IDs for revisions and instances
VB-17 Adopted §12.6 Harvest and avoid table for versioning; AC-M0-04 wording goes to the acceptance drafter
VB improvement 1 (storage blueprint) Adopted §12.1 Blueprint extended with policy records, settings audit, presentation, alias, entity type
VB improvement 8 (harvest/avoid) Adopted §12.6 As above
DC-06 Adopted §8.5, §8.6 Phase-1 O(1), drain, digest re-check, manifest after commit, predicate sweep; mechanics in the consistency model
DC-07 Adopted §7.6 Brief §1.8 model restated as rules
DC-08 Adopted §7.5, §8.4, §9.7, §10.4 Derived records carry their version vector; readers fail closed; mechanics in the consistency model §8
DD-07 Corrected §7.1, §9.1 Task keyed (study, form); versions as state; reconciler session an entity of the task
DD-10 Corrected §8.4, §13 The two designs collapsed to derived effects per brief §1.1; DD-10's materialised alternative recorded and not chosen; D2-01
DD-12 Adopted §3.1 System entity-type IDs minted at F1a; O1 depends on them
DD-15 Adopted §7.1 Deterministic SessionId; FormSession created on first autosave
DD-26 Adopted §3.1, §6.4 definitionOwner versus owningParent; candidate child never attaches to a reconciled parent (C1 test)
PH-06 Adopted §3.4, §10.2 Response modes and metadata in version content; suppressed answers preserved; exports resolve suppression; frozen versions settle the open question
PH-18 Adopted §3.5, §7.2, §8.2, §8.6 Transitivity as classes; resolved question set stored per session version; FEAT-003 categories in the manifest and the four-step U6 flow
PH-33 Adopted; disposition Question §3.9, §7.6 No autosave trail (brief §1.8); multi-admin editing via pending-edit leases; training rounds D4-04
SR-16 Adopted §3.5, §8.2, §8.3, §10.2 autoUpdate only within a class with valid values; one-to-one mappings only; rationale stored; qualificationPolicy and answeredUnderVersion exported
MS-03 Adopted §8.6, §10.1 draft_only counted from pmSessionDraft; usage family over explicit versions only
V2-18 Adopted; container PROPOSAL §7.1, §7.6, §9.7 Candidate key stays (study, form, reviewer); reconciler session on the task; adjudications are revisions; ReviewSession generalisation for screening-only steps at F3/F5