Versioning model¶
Revised after the owner session (5 October 2026). Chris's owner session of 4 and 5 October changed this rulebook in ten places. (1) D2-01 is reversed: publication may create attributable generated session versions (
PublicationGenerated), so effects are no longer always derived (§8.11 carries RD §3.15's before and after tables). (2) The form's standard reviewer target is part of the immutable form version: a target change is a new form version with publication impact (§4.6, §4.7, §8.12). (3) Compatibility is declared explicitly by the publisher and is immutable from commit; corrections use guided rollback and republication (§3.5, §8.13). (4) Q-34 is decided: option mapping writes new immutable revisions (§8.9). (5) Screening profiles have immutable versions whose outcome-shaping settings live inside the version, and profile publication detects mismatches and previews them (§5.1, §5.5). (6) Every other setting is classified as inside the version or operational (§4.6). (7) Study target overrides version per Study × form and get an explicit treatment when the standard target changes (§4.7). (8) Autosave keeps a reconstructable draft change log and superseded drafts are rebased (§7.7). (9) Training references and policies and (10) AI screening model configurations are versioned records (§16). The owner-session consolidation wins over older text, and the specifications carry the detail (mainly RD). Superseded rules are kept and marked "Superseded by the owner session (5 October 2026)"; nothing is renumbered. Feature implementation remains on hold (Chris, 5 October 2026). These are planning decisions only; brief approval and implementation authorisation are separate per gate (D1-04) and still on hold; no work has started, no gate has passed and nothing is enabled in production. Thresholds quoted here are proposed, not approved. The owner-session amendments are listed in the owner-session amendments record at the end.
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.
Owner session (5 October 2026): the paragraph's "not a writer of evidence" is superseded. Publication records a policy and may write attributable generated session versions and mapped revisions under the generation table in §8.11; where it writes none, effects stay derived. The standard target is inside the form version and every other setting follows the classification in §4.6, so "operational settings" now means the settings that change only who is offered work and when. Compatibility declarations are made by the publisher and never change after commit (§3.5).
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. Owner session (5 October 2026): Q-34, Q-26, Q-04 and Q-36 decided; Q-29 decided-amended; Q-35 removed (§15.1) |
| Owner session (5 October 2026): publication-generated versions (D2-01 amended), the publisher's immutable compatibility declaration with guided rollback (D2-02 amended), the target inside the form version (target-versions-form; D2-05 amended in part), Study target overrides, profile versions with mismatch preview (Q-26), the draft change log (D2-07, D2-08 decided), training and AI model configuration versions | OWNER for the decisions; the generation table, the setting classification beyond the target, the override treatment set, the rebase mechanics and every storage shape are PROPOSALs for the brief |
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; owner session: the publisher's declaration and any option mapping are immutable from commit) |
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; owner session: accepted result versions and readiness snapshots | Everything once published: ordered pins with ancestors, requiredness, applicability graph, renderability result. Owner session: also standardTarget and ReconciliationPolicy (§4.1, §4.6) |
Usage, impact categories | Publication starts the FV2 flow (owner session: including a target-only change, §8.12) |
| Form settings | formId head | Nothing | Nothing; every change appends an audit entry (§4.4) | Sufficiency and readiness read the live value (owner session: the target is no longer here; sufficiency reads the effective target, §4.7) | Never |
| Profile criteria version | (profileId, seq) | Decision revisions, profile-owned answer revisions, stage settings versions; owner session: outcomes, adjudication versions, stage filter clauses, external decisions | Everything once published: eligibility question pins, decision rules, must-agree set, requiredness. Owner session: sufficiency rule, Unsure, tie policy and bound, discussion, adjudication rationale, reason handling, identity blinding and the ScreeningSourcePolicy (§5.1) |
Decision standing under Q-26 | Profile publication starts the Q-26 flow (owner session: with mismatch detection, §5.5) |
| Profile settings | profileId head | Nothing | Nothing; audited (§5.1) | Routes and DP5 read live (owner session: superseded, they are version content) | Never. Owner session: what remains here is the PRISMA phase mapping reference, optional adjudication-task expiry and keyword lists; the adjudicator assignment is its own versioned record (§16.5) |
| 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. Owner session: the embedded StageStudyFilterVersion, the progression basis, step kinds, exclusion-stop, saved-work and departure-continuation settings, an optional stricter route cap, the hide-only hint override and browsing; route policies, BL1 and the VS1 default are superseded (§5.3) |
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. Owner session: kinds also PublicationGenerated, MergeResolved, MergeCarriedForward, UnmergeCarriedForward; the generating operation and generation; admissionBasis |
Per-answer state, requirement standing, qualification (§7.4, §7.5) | Never (owner session: a publication may create one per affected session per generation, §8.11) |
| Draft | sessionId (one record; bounded conflict copies) | Nothing | Nothing; lease, etag, patches (§7.6). Owner session: the head moves by CAS on draftVersion and each accepted autosave appends an immutable SessionDraftChange (§7.7) |
Draft-changes flag; draft_only category |
Counted in the manifest, never transitioned (owner session: rebased when its base is superseded) |
| 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 (owner session: every effect not written as a generated version; generated versions reference the operation and generation) | 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). Owner session: the system scope is the CAMARADES catalogue (CatalogueItemVersion, §16.5) |
| Claim | Not a version kind. Claims are keyed by form or profile identity, never by version (brief §2.1, RT-11) | ||||
| Design draft (owner session) | draftId with scope (form or profile); changes (draftId, draftRevision) | Nothing; a published version records the actor, time and base revision of each change it contains | Every DesignDraftChange; the head moves by CAS |
Draft working state | Never; publishing from a draft is the impact point (§3.9) |
| Study target override (owner session) | (project, study, form); versions (overrideId, seq) | Accepted result versions, readiness snapshots | Every version (value, basis, made against form version and standard target, reason, actor, time) | Effective target (§4.7) | None by itself; a standard-target publication gives each override an explicit treatment (§8.12) |
| Study version (owner session) | (studyId, seq) | Study.currentVersionId; merge and unmerge records |
Every version, including per-field provenance and reference and source-document links (§16.1) | Displayed bibliography | Never; merges and unmerges are C21 operations |
| Stage study filter version (owner session) | (stageId, filterSeq) | Stage settings versions; pool events; ReviewStartEligibility |
The clause tree, dependency set and digest | Pool membership at read (§16.2) | Never for sessions; activation runs a pool history sweep |
| Accepted result version (owner session) | (project, study, form, seq) | Gold snapshots; queries; exports; stage filters through filterInputs[] |
Authority, acceptance method, origin, input set, form and override versions, effective target, actor, snapshot (§9.8) | Standing (Current, InputsChanged, BelowCurrentTarget, SuspendedByTarget, NeedsReReconciliation, Superseded) |
Never; a target or policy change creates a new version only through the entitled rule or person |
| AI screening model configuration (owner session) | (project, configurationId, seq) | Profile versions (source policy), runs, decisions | Every version (§16.3) | Nothing | None by itself; a profile version must pin a new version to use it |
| External screening decision (owner session) | (project, profile, sourceKey, study, seq) | Outcomes and adjudication versions as inputs | Every version (§16.3) | Current pointer (latest accepted) | Never |
| Training reference and policy versions (owner session) | (project, referenceId, seq); (project, policyId, seq) | Training steps (through stage settings versions), attempts, assessments | Every version (§16.4) | Attempt results under the pinned versions | Never for live work |
| Adjudicator assignment, classification rule, catalogue item and notification content policy versions (owner session) | Per record (§16.5) | Adjudication tasks; inference results; project copies; email shaping | Every version | As each record says | Never |
Rules that hold for every kind:
- Identity never changes. A structural change creates a new identity; lineage is recorded by
reference (
derivedFrom,copiedFrom,supersedes), never by rewriting. - 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).
- Pins reference exact versions by ID. A reader never resolves "latest" to find what a stored record meant.
- 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. Superseded by the owner session (5 October 2026; D2-01 amended): a publication may write attributable generated session versions and mapped revisions under the generation table in §8.11; option retirement and definition changes still write nothing by themselves; where a publication generates nothing, effects stay derived.
- 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. Owner session: a standard-target change is a form publication and so starts the impact flow (§8.12); changing an operational setting or a Study target override shows an active-work impact preview but is not a publication.
- Operational settings never version. They change with an audit entry and are read live (D2-05). Owner session (D2-05 amended in part): the standard target is inside the form version; every other setting is classified by §4.6, and settings that change what counts as valid evidence, how evidence is produced or how an accepted result is formed are version content.
- Time is transaction time. Order comes from per-aggregate sequences and the HLC commit stamp
(consistency model §11);
observedAtand legacyDateTimeCreatedare 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). Owner session: compatibility {suggestedBySyrf, declared, declaredBy, declaredAt, rationale} and optionMapping {oldOptionId → newOptionId, declaredBy, declaredAt}, declared by the authorised publisher (RD §3.4) |
| 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:
- 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}. - Canonical answers store option IDs; values and labels are display data resolved from the pinned version. Exports carry both (§10.2).
- Renaming
valueordisplayLabelkeeps 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). - 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.
- Adoption mints IDs per legacy value, reusing
OptionInfo.Idwhere 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; thedefinitionVersionstamp is the revision'squestionVersionRef. - 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.
Superseded by the owner session (5 October 2026; D2-02 amended, Q-34 decided; OWNER).
Compatibility belongs to the immutable question version. SyRF suggests a default from the diff; the
authorised publisher explicitly declares compatibility and whether option mapping is
appropriate, with actor and time. A question version is normally committed as part of publishing
the first form or profile version that pins it, so the declaration is the publisher's at that
moment. The declaration and the mapping are immutable from commit, so the "before that it may
change" window above no longer exists. A meaning change must be declared incompatible. Mapping never
bypasses type or option validation, and invalid or mandatory re-review constraints cannot be
bypassed. A wrong declaration is corrected by guided rollback and republication (§8.13): a
corrected question version with the same content and the corrected declaration, a new form version,
and generated versions that restore or re-treat the affected sessions; the wrong declaration stays
in history with a "corrected by" link. In the table below, "Admin may" reads "Publisher may"
(RD-R20, RD-R21).
| 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; owner session: Q-34 decided, the mapping is explicit per option and allowed only where meaning is unchanged; a declared meaning change cannot be mapped) |
| 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:
classSeq(r.questionVersion) = classSeq(v); otherwise validity is not evaluated and the answer's state isNeedsUpdatingVersion(§7.4);- the payload shape matches
v's data type and selection multiplicity (true by construction inside a class); - for option answers, every selected
optionIdis active inv, or an applied mapping (§8.9) has produced a policy-derived successor revision that is valid; - a response mode used by
rexists inv, and metadata validate againstv's fields; 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):
- System questions are stored as data in a global
pmSystemQuestionVersioncollection keyed(systemGuid, systemQuestionVersion, seq)with astructuralDigest, seeded idempotently from code at start-up and never rebuilt per read on canonical paths (VB-11). The seed forseq = 1is today's definition for eachsystemQuestionVersionthat exists in production. - Identity is
(systemGuid, systemQuestionVersion)(§3.1), so the v0 and v1 structural variants are distinct identities.definitionOwner = system. - CAMARADES publishes content versions through the designer with the same compatibility declaration as any question (application role; D2-15 for template curation).
- 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.
- 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.csquestion 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; owner session: immutable from commit, whether or not anything pins it) |
| 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.
Superseded by the owner session (5 October 2026; D2-11 decided, collaborative question drafts
amendment OS-A01; OWNER). Pending edits live in design drafts. A DesignDraft is a named,
unpublished proposal for the next version of one form or profile, including pending question edits;
several may exist for one form. Each accepted edit appends an immutable DesignDraftChange
(draftId, draftRevision) recording the actor, time, item changed, the item revision it was based on
and the before and after values; the draft head moves by CAS. A stale base is refused and the local
edit is kept beside the current value for "keep mine" or "discard mine"; nothing is overwritten
silently. Accepted changes appear in every open view while unsaved local edits survive. Authorised
presence (viewers and editors) is shown only to users with design access, lives in hub connection
state and is never an audit record. A draft never changes a published requirement or a reviewer's
form and never starts an impact flow. Only one publication operation per form runs at a time; a draft
published after another rebases item by item and recomputes its impact against the resulting state
(RD §3.7, §4.7; RD-R25, RD-R26). The design-draft change log is the pending-edit trail.
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 (ASSUMPTIONA-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);- owner session (5 October 2026):
FormVersion.standardTarget, the form's standard reviewer target (target-versions-form,OWNER), andReconciliationPolicy{targetOneHandling∈AutoAccept,RequireHumanReconciliation(R1,OWNER; baseline conversion alone may setNoAcceptance, RS-R04aPROPOSAL);acceptedCompleteness;identityBlinding;reconciledHints;outdatedAnswerCompletion}, the last four classified inside the version asPROPOSALs (§4.6).
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. Owner session: so does changing the standard target or the reconciliation policy, even when no question changes (§8.12).
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:
- 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
FormVersionNotRenderableerror. - 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. - 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). Superseded by the owner session (5 October 2026): the standard target is inside the form version (§4.1, §4.6) |
| Optional capacity cap | D3-17 (off by default; separate from the target). Owner session: the form-owned CapacityCap baseline applied through every route; a stage may set only a stricter route cap; defaults proposed, not approved |
| Reconciliation compare settings | C9 |
| Gold-completeness policy | Q-04 (OPEN). Owner session: Q-04 decided; the completeness override is inside the form version (ReconciliationPolicy.acceptedCompleteness, classification PROPOSAL) |
| Form-level guidance | A-15 |
| Bulk-approve flag | Q-11 (OPEN, after R4a). Owner session: Q-11 decided; bulk acceptance is an operational setting, off by default |
| Inactivity timeout; per-reviewer in-progress limit (owner session) | D2-07, Q-28: form-owned across every route and tab; defaults not approved |
| Unstarted reconciliation-assignment expiry (owner session) | Q-30: optional, admin-configured, off by default; the profile holds the equivalent for adjudication tasks |
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.
Superseded by the owner session (5 October 2026; target-versions-form, OWNER): sufficiency
reads the effective target (§4.7), and changing the standard target creates a new form version and
goes through the publication impact process, even without question changes (§8.12). The FEAT-024
definition-rewrite fence is admitted in that publication's phase 1 as for any definition move.
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). Owner
session (D2-15 amended): the system-scoped templates are one application-wide CAMARADES catalogue of
immutable CatalogueItemVersions; a copy records copiedFrom {catalogueId, itemId, versionId,
copiedBy, copiedAt} on the new canonical definition records; a project edit never changes the
catalogue and a new catalogue version never changes a copy; adopting a newer catalogue version is an
explicit copy into a new project version through ordinary publication (ACD §3.4).
4.6 Setting classification (owner session, D2-05 amended in part)¶
The owner fixed one row: the standard target is inside the form version. Every other row is a
PROPOSAL for the brief, using one rule (RD §3.5):
a setting goes inside the version when it changes what counts as valid evidence, how evidence is
produced or how an accepted result is formed, because the records it affects must pin it; a setting
stays operational when it changes only who is offered work and when. Operational changes are
audited, read live, recorded on the events that use them, and preceded by an active-work impact
preview where they affect people working now.
| Setting | Owner | Classification | Basis |
|---|---|---|---|
| Question pins with ancestors, requiredness, minimum instances, applicability graph, renderability result | Form | Inside the form version | FV1; §4.1 |
Standard reviewer target (FormVersion.standardTarget) |
Form | Inside the form version | OWNER (target-versions-form) |
Target-one handling (AutoAccept, RequireHumanReconciliation; conversion-only NoAcceptance) |
Form | Inside the form version | OWNER (R1); NoAcceptance is PROPOSAL (RS-R04a) |
| Accepted-answer completeness override | Form | Inside the form version (PROPOSAL) |
Q-04 |
| Reconciliation identity blinding | Form; the profile for screening reconciliation and adjudication | Inside the version (PROPOSAL for forms; profiles version it) |
Q-28, Q-30, blinding amendment |
| Reconciled-answer hint baseline | Form; a step may only hide | Inside the form version (PROPOSAL); the step override is in the stage settings version |
Q-28 hints amendment |
| Outdated-answer completion mode | Form | Inside the form version (PROPOSAL); the mode in the declared version applies |
D4-17 (O3) |
| Sufficiency, Unsure, tie policy and bound, discussion, adjudication rationale, reason handling, source policies | Profile | Inside the profile version (tie policy by owner decision; the others PROPOSAL) |
S1, S2, Q-32, S3, E3; §5.1 |
| Inactivity timeout; per-reviewer in-progress limit | Form | Operational (PROPOSAL) |
D2-07, Q-28 |
| Capacity cap (separate from the target) | Form baseline; stricter route cap in the stage | Operational, off by default (PROPOSAL) |
D3-17 |
| Unstarted assignment expiry | Form (tasks); profile (adjudication) | Operational, optional, off by default | Q-30 |
| Bulk acceptance | Form | Operational, off by default | Q-11 |
| Adjudicator assignment | Profile, as AdjudicatorAssignmentVersion |
Its own versioned, audited record; no profile publication | Adjudicator assignment amendment |
| Comparison display; guidance text | Form | Operational | D2-05 |
4.7 Standard target, Study target overrides and the effective target (owner session)¶
- Effective target (derived, never stored as a fact; projected in
Study.currentEvidence): the currentStudyTargetOverridevalue for the Study × form unless it isRemoved, otherwise the current published form version'sstandardTarget(OWNERconsolidation §1; precedence RS-R05PROPOSAL). There is no step-level override, and a stage never supplies a target (RD-R31). StudyTargetOverrideis an immutable, versioned Study × form requirement decision applying across every route: versions(overrideId, seq)withvalue,basis(SetByAdmin,AdditionalReviewRequest(requestId, raiseBy),MergeConfirmation(mergeId)orRemoved),madeAgainst {formVersionSeq, standardTarget}, reason, actor, time and clock stamp. Changing an override creates no form version (RD-R14). An additional-review request raises the effective target through an override version per Study and counts qualifying reviewers once across routes (RD-R17).- Pins. Accepted result versions and readiness snapshots record the form version and any override version they used (RD-R15), so a later target change never reinterprets them.
- Override treatment at publication. When a new standard target is published, the preview lists
every override with an explicit treatment: keep its effective value, rebase it, or remove it. The
suggested default keeps each Study's effective target unchanged and flags overrides that become
redundant or fall below the new standard; no effective target changes silently (RD-R16; the
treatment set is a
PROPOSAL, the explicit definition is the owner's requirement). - Results. A target change may lead to a new accepted-result version under the agreed authority rules; it never rewrites an old result, invents a missing review or invalidates a recorded answer (RD-R29; §9.8).
- Statistics. General statistics show the standard target and list study-specific exceptions separately; per-Study completion uses the effective target (RD-R19; RD ambiguity 2).
- The target is a minimum and stays separate from any enforced capacity cap (RD-R18).
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). Owner session (5 October 2026): Q-26
is decided; the "only before any decision exists" restriction is superseded by the mismatch
detection and treatment in §5.5.
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).
Superseded in part by the owner session (5 October 2026). Profile settings that change how
outcomes are derived are version content of the ScreeningProfileVersion and change only
through profile publication with an impact preview: the sufficiency rule (initial decisions and
agreeing definite decisions required); unsureEnabled (D4-01 decided, on in the proposed
title/abstract template); tiePolicy ∈ ExtraReview with an extraReviewBound, or Adjudication,
with handling per trigger (decision tie, unresolved Unsure, reason disagreement, sole-source AI-model
Unsure) and no inferred default (tie amendment, OWNER); discussionEnabled (D4-02 decided, with an
optional admin-set time limit and no default duration); adjudicationRationaleRequired (Q-32, off by
default); reason handling (DP5, must-agree answers RX1, primary-reason derivation as template guidance,
no question-count limit; S3, D4-13); identityBlinding (Blinded default, blinding amendment); and
the ScreeningSourcePolicy (E3; the slot is reserved at F5 and populated only from XS1, §16.3). The
tie policy's placement is the owner's decision; the others are RS's classification (PROPOSAL). The
profile head keeps only the PRISMA phase mapping reference, optional adjudication-task expiry (Q-30,
off by default) and keyword lists if profile-owned (PH-28). The adjudicator assignment is a separate
AdjudicatorAssignmentVersion (§16.5) whose change needs no profile publication
(RS §3.7, §3.12).
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.
Owner session (5 October 2026; Q-15 replaced, Q-01, Q-24, Q-28 decided; SP §3.2 to
§3.4): the settings version embeds the
StageStudyFilterVersion (§16.2), replacing the filter element; the route policies (the DP6/DP7
cross-stage part) are superseded because no incoming stage-entry gate exists; BL1 and the VS1 default
leave the stage (blinding and the hint baseline belong to the form or profile version, §4.6), and a
step may only hide hints; EW1 becomes the saved-work setting. The version adds the progression basis
(OwnIncludeSufficient default or CollectiveIncludeRequired) with a tighten-only step override,
step kinds (review, system adjudication, training), extra screening after sufficiency (Stop default
for new combined steps), exclusion-stop (personal, collective, scope), the started-work-after-departure
setting (placement SP-AMB-01), an optional route capacity cap stricter than the form's baseline, and
the browsing setting. The capacity cap baseline, the inactivity timeout and the in-progress limit move
to the form's operational settings, and the most-restrictive-stage rule for them is superseded. A
settings version that changes only steps keeps its filter version. Publishing a settings version
still changes no session pin and no qualification; a new filter version starts a pool history sweep
(C20).
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). Owner session: D2-04 is an engineering contract with no owner question; the recommendation stands and the effective target never comes from a stage (RD-R31).
5.5 Screening profile publication and mismatch preview (owner session, Q-26 decided)¶
Screening profiles have immutable versions (OWNER, Q-26; RD-R27). Publishing a profile version:
- Preview. The proposed version is compared with every profile version still in use by existing
decisions, collective outcomes and adjudications. The preview shows each mismatch and its
consequence per Study, active screeners and adjudicators, dependent stage pools and accepted
results, open ties,
AwaitingExtraReviewandPendingAdjudicationStudies, open discussions, in-flight extra decisions, and whether eligibility changes and so needs a protocol amendment entry in the same publication (D4-05). - Treatment. The admin chooses, at profile scope and never per stage: keep decisions pinned to
their version; request new decisions; or carry compatible decisions forward as attributable
generated profile-session versions. Open adjudication tasks and discussions created under the old
version continue and their results stand; Studies with no open work item are re-evaluated under the
new version only as the chosen treatment says (RS §4.12,
PROPOSAL). - Recheck and phases. Phase 1 as for forms (§8.5) with a final recheck at commit. Phase 2 works per Study: generated profile-session versions where the treatment carries forward or requests renewal, the collective outcome recomputed under the chosen policy, the Study write and notices.
- What never changes. Original decisions, outcome history and frozen reports. Required new screening stays visibly pending. Changed outcomes trigger targeted pool reevaluation with structured history (C20); accepted or adjudicated results whose inputs changed follow the explicit reconsideration rule (RS-R58).
- Source policies. A change to a
ScreeningSourcePolicy(adding or removing an external or AI source, or changing its role, scope, mapping or the pinned model configuration version) is a new profile version through this flow; decisions accepted under the earlier version keep it (§16.3; RI-R35).
Fixture: FX-VM-51 (replacing the parameterised FX-VM-40 behaviour for decided Q-26).
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 (keySchemaVersionincluded in the hash input); - unique indexes are on scalars only:
{projectId, contextKeyHash}unique; per kind, partial unique indexes such as{projectId, studyId, authorScope, profileId}wherekind = ScreeningDecision; - non-unique
{projectId, studyId, authorScope, questionId}serves ancestor and SF5 reads, and{projectId, questionId, currentQuestionVersionSeq}serves usage per question version; - the head
_idis 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).
Owner session (5 October 2026): a mapped revision written by a publication keeps authorship =
policyDerived with the reviewer as effective author and records the operation and generation
(§8.9, §8.11); merge-created revisions on a consolidated Study copy the source revision's authorship
and add merge provenance to the exact source revision (C21); a SingleAnnotator acceptance writes
reconciled-scope revisions with derivedFrom the candidate revision and the system rule as actor
(RS §3.2); a revision written by hint click-to-fill is an ordinary reviewer revision carrying
adoptedFrom (RS-R34); promoted training answers carry promotedFrom (TI §3.6); imported is limited
to mapped human annotation imports (XA1). Training sessions use author scope training(attemptId) and
never share heads with live work.
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.owningParentis 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:
- Identity is the label head ID in the author's scope, minted once from a client-proposed,
server-validated GUID (§12.3).
entityPathelements 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). - Rename is a new revision on the label head. Identity, answers and cells are untouched.
- 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
OutdatedOwnAnsweron those heads; nothing is pruned. Outcome cells keyed by the instance become inapplicable by derivation. - Duplicate mints new instance IDs; copied revisions carry
copiedFrom = sourceRevisionIdand the reviewer's authorship. - Population membership is an attribute of the instance; the default population needs no document (DD-24). Outcome cells (O1) key on instance IDs.
- 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). Owner session: D2-07 and D2-08 are decided; one session and one place serve every stage, tab and device, and the form owns the inactivity timeout and the in-progress limit (§7.7).
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. Owner session: kinds also include
PublicationGenerated (with the operation and generation, deterministic ID per session, operation and
generation) and MergeResolved, MergeCarriedForward and UnmergeCarriedForward (with the merge or
unmerge and the exact source versions); every version records admissionBasis (C5, SP §3.8), and the
declared form version fixes the outdated-answer completion mode that applied. 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) |
PublicationGenerated (owner session) |
a publication operation, attributed to the publisher; the reviewer stays effective author of the answers | one session version per publication generation, re-pinned to the new form version, with the same revision pins or mapped revisions as the generation table says (§8.11) | computed against the latest head and never overwrites a newer reviewer version; completed only if the pin map validates under the new version, otherwise incomplete; draft-only sessions get none (their draft is rebased) |
MergeResolved, MergeCarriedForward, UnmergeCarriedForward (owner session) |
a merge or unmerge operation for the committing user; the actual resolver recorded for resolved conflicts | a session version on the Study the operation targets, copying or resolving current state from exact source versions | Complete only when validation passes under the declared form version; drafts are never carried (C21) |
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).
Superseded by the owner session (5 October 2026; D2-01 amended): the PublicationGenerated
transition above exists. A late Save on a superseded version is still accepted pinned to it where no
version was generated for that session; where one was, the Save is refused as stale and the draft is
rebased (RD-R24, §7.7, §8.8).
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 versionEpinned tofv_Eand the recorded policies for the publications between them (the latest generation of each, §8.7): fv_E = fv_cur→satisfiesif every required applicable answer isCurrentorOutdatedOwnAnswer, else the session hasNeedsAnsweringorNeedsUpdating*answers;fv_E < fv_cur→ evaluate each question offv_curunder its treatment (§8.2): unchanged → satisfied by the pinned answer;added→ satisfied only undercountEarlierCompletes;changedCompatiblewithautoUpdate→ satisfied iff valid;requireReanswer→ not satisfied until a revision pinned to the new class exists;doNothing→ the session ispinnedOlder, 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). Superseded by the owner session
(5 October 2026): SF6 now applies literally; under requireReanswer on an answered question the
publication generates an incomplete PublicationGenerated version, so the completed session stops
qualifying through an explicit, attributed version. Derived standing remains for the cases where the
generation table writes nothing (§8.11). Qualifying also requires that no active
ContributionExclusion covers the contribution (O1). 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.
Superseded in part by the owner session (5 October 2026), see §7.7: the lease and "Take over editing" bullet (D2-08 decided: one shared session across tabs and devices with base-version checks; the take-over presentation is a brief detail); the "no autosave trail" bullet (replaced by the draft change log); the stale-autosave rejection (replaced by rebasing); and the D2-07 middle ground (replaced by the decided form-owned timeout and in-progress limit). The no-TTL, audited-discard, cross-form conflict and consume-on-Save bullets stand.
7.7 Draft change log, rebase and the shared place (owner session)¶
Rules (OWNER for the behaviour, consolidation §1, D2-07, D2-08, Q-20; mechanism PROPOSAL;
RD §3.10, §3.11, §4.2, §4.6):
- Diffs with per-answer bases. Autosave sends only changed answers, each with its per-answer base.
The draft head records the base explicit version, the base form version and an increasing
draftVersion, and moves by CAS. Each accepted autosave appends an immutableSessionDraftChange{draftVersion, basedOnDraftVersion, per-answer base, patch, connectionId, tabId, actor, at}topmSessionDraftChange. The draft rebuilds byte for byte from its base and change log, so its history is fully reconstructable (replaces PH-33's "no autosave trail"). Save and Complete consume the draft atomically and link its changes to the version they produced. Retention and any compaction are brief items; compaction never removes an explicit version. - Concurrent tabs and devices. Two connections may autosave. A change to an answer that has not moved since its per-answer base applies; a change to an answer that moved becomes a recoverable conflict copy and both values are shown. A read-only take-over presentation may be chosen in the brief for small screens.
- Rebase. A draft whose base was superseded, by a
PublicationGeneratedversion or by the reviewer's own Save in another tab, is rebased onto the new version at the next autosave or Save: non-overlapping changes apply, overlapping ones are kept with both values, and nothing is discarded silently (RD-R24). A late Save on a superseded base is refused as stale where a version was generated. - The shared place. One place per reviewer per Study × form across every stage, tab and device. The form owns the inactivity timeout and the per-reviewer in-progress limit. One idle or closed tab never releases the place while another connection is active; when every connection is idle beyond the timeout or closed, the place is released and the draft is kept; on return admission and capacity are rechecked and SyRF says honestly if no place is free (RD-R11). The in-progress limit counts sessions, never connections.
- Status words. "Draft auto-saved" and "Version checkpoint saved" stay distinct from completion; the words are illustrative (D3-03 decided-amended).
Fixtures: FX-VM-32 (amended), FX-VM-47.
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). Superseded by the owner session (5 October 2026; D2-01 amended): a publication records a policy, starts an operation and may write attributable generated session versions and mapped revisions under §8.11. A standard-target change is a publication (§8.12).
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. Superseded in part by the owner session (5 October 2026): generated versions apply the chosen treatment explicitly where §8.11 says so; the evaluator still derives standing for every session the publication did not generate a version for, and for every session it did, from the generated version like any other explicit version. 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 < opswept 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).
- Owner session (5 October 2026): phase 2 also writes, per Study item in one short transaction, the generated session versions (CAS on each session head against the version the plan used, recomputing if the reviewer saved meanwhile; deterministic ID per session, operation and generation), mapped revisions, result and override versions under the confirmed treatments, the Study write and notice intents; a crash resumes and replays return existing versions. Phase 1 still does constant work, admission and readiness for the form still pause while phase 2 runs, and the pause and operation limits are measured (D2-10 brief item; the 90-second and 30-minute figures are proposed, not approved).
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. Owner session: a new generation may generate further versions,
for example re-pinning a session to the form version under which its Complete was validated to
restore its completed status; earlier generated versions stay in history.
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). Owner session: still true
where no version was generated for the session (for example under doNothing); where the publication
generated a version, the late Save is refused as stale and the draft is rebased onto the generated
version (RD-R24, RD-AE18).
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.
Owner session (5 October 2026; Q-34 decided, OWNER). Option mapping is approved as recommended:
the publisher declares it explicitly per option, only where meaning is unchanged, and it writes new
immutable revisions with mapping provenance while keeping the originals; it never bypasses type or
option validation or a mandatory re-review (RD-R21). It is no longer "the single exception": mapping
is one of the generated-version cases of §8.11, and the session's pin map moves to the mapped
revisions within that generated version.
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. Owner session
(D2-11 decided): many design drafts may exist; the next publication rechecks its impact against the
state the previous one produced (§3.9).
8.11 Generated session versions (owner session; D2-01 reversed)¶
The decision. The round-2 proposal said publication writes no evidence, so a published policy
would change only what readers derive. Chris rejected it: publication may create attributable
generated session versions (OWNER; register "Decisions already fixed"; consolidation §1). SF6
already anticipated this, so SF6 now applies literally.
When publication generates a version (PROPOSAL for the brief; RD §3.15). For each affected
session whose latest explicit version pins an older form version and falls in a category the admin
chose to treat:
| Outcome of the recorded treatment for the session | Generated version? | What the generated version holds | Resulting status |
|---|---|---|---|
Every changed question is doNothing, or no question changed (a target-only or policy-only version) |
No | Nothing | Standing derived: pinned to the older version, counted or not as the admin chose; unchanged questions satisfy the new version |
autoUpdate on compatible changes and every pinned answer is valid |
Yes, PublicationGenerated |
The same revision pins, re-pinned to the new form version | Completed stays completed only if the pin map validates under the new version |
| Option mapping (Q-34) | Yes | New revisions for mapped answers with provenance {sourceRevisionId, mappingRef, operationId, generation}; pins moved to them |
As validated |
requireReanswer on an answered question |
Yes | Pins kept; the answer shows Needs updating; re-pinned to the new version | Incomplete; a completed session stops qualifying |
New required question with requireAnswerBeforeCounting |
Yes | Re-pinned to the new version; the new question needs an answer | Incomplete |
New required question with countEarlierCompletes |
No | Nothing | Standing derived: pinned older and counted |
| Category outside the admin's chosen scope | No | Nothing | Standing derived |
| Draft-only session (no explicit version) | Never | The draft is rebased (§7.7) | Not applicable |
One session gets at most one generated version per publication generation, combining every
question's treatment. Generated versions are attributed to the publication operation and the
publishing admin; the reviewer stays the effective author of the answers. A generated version never
records a Complete that fails validation under its declared form version and never overwrites a newer
reviewer version. Whether autoUpdate generates a version or stays derived is confirmed in the
brief; the recommendation is to generate, so that a session's current version pins the form version
it satisfies, with the cost measured under D2-10 (RD §12).
Before and after for this rulebook (RD §3.15).
| Section | Before | After |
|---|---|---|
| §1.1 model paragraph; §2 rule 4 | Publication is a recorded policy and never writes evidence, with option mapping as the only exception | Publication records a policy and may write attributable generated session versions and mapped revisions under the table above; where it writes none, effects stay derived |
| §2 rule 6; §4.4 | Operational settings never version; the minimum target is an operational setting | The standard target is inside the form version; other settings follow §4.6 |
| §3.5 | A declaration may change until a revision pins the version | Immutable from commit; declared by the publisher; corrected by guided rollback and republication (§8.13) |
| §3.9 | One designer pending record with a lease and take-over | Collaborative design drafts with per-item base checks |
| §7.2, §7.3 | Kinds Save, Complete, Withdraw; "there is no publication-written transition" | Kinds include PublicationGenerated and the merge kinds; generated transitions follow the table above |
| §7.5 | SF6 read as "changes the effective standing" | SF6 applies literally when a version is generated; derived standing remains for the no-generation rows |
| §7.6 | No autosave trail; second tab read-only with take-over | Draft change log kept; concurrent tabs reconciled by base checks; the take-over presentation is a brief detail (§7.7) |
| §8.1, §8.4 | "Nothing is applied to a session" | Generated versions apply the chosen treatment explicitly |
| §8.5 | Phase 2 rewrites projections only | Phase 2 also writes generated versions and mapped revisions, per Study, idempotently |
| §8.7 | FV4 appends a policy generation | Unchanged, and a new generation may generate further versions; earlier generated versions stay in history |
| §8.8 | A late Save on a superseded version is accepted pinned to it | Still true where no version was generated; where one was, the Save is refused as stale and the draft is rebased |
| §8.9 | Option mapping as the only derived-revision writer, OPEN |
Q-34 decided; mapping is one of the generated-version cases |
| §9.3 | Any candidate version change drifts the task | A generated version drifts only the questions whose pinned revision or class changed, or a candidate that stopped qualifying |
What does not change. Phase 1 does constant work (fence, drain, digest recheck, head CAS, policy and operation records). One publication per form. Policy records are append-only generations. The impact manifest is built after commit and is never an input to an effect. Admission and readiness for the form pause while phase 2 runs.
Fixture: FX-VM-46.
8.12 Target-only publications and the override treatment (owner session)¶
A change to FormVersion.standardTarget creates a new form version and uses the publication impact
process even without question changes (target-versions-form, OWNER; RD §4.9).
- Preview. Studies whose sufficiency changes (below or above the new target); reconciliation readiness changes; accepted results that would change authority; Studies with overrides and their treatment (§4.7); active places beyond the new target where capacity enforcement uses it.
- Treatments the admin confirms. For a raised target: keep existing accepted results current,
labelled "below current target" (default), or suspend them pending reconciliation with the new
number of reviewers (RS-R10,
PROPOSAL). For a lowered target withAutoAccept: createSingleAnnotatorresults now, or at the next qualifying completion, for Studies that now have exactly one qualifying candidate and no accepted result (RS-R11); Studies with several agreeing candidates are never auto-accepted. For each override group: an explicit treatment (keep the effective value, rebase, remove). A switch toRequireHumanReconciliationkeeps existingSingleAnnotatorresults or queues their tasks for human reconciliation (RS-R12). - Writes. Phase 1 as §8.5 with no question versions. Phase 2 per Study: result versions under the confirmed treatment, override versions where the admin chose to change them, the Study write. No session version is generated.
- Afterwards. Effective targets and readiness at query time; automatic stage completion re-evaluates; stage filters that read accepted answers re-evaluate the affected Studies (C20). Raising the target invents no reviewer, vote or placeholder.
Fixtures: FX-VM-42 (rewritten), FX-VM-48, FX-VM-49.
8.13 Guided rollback and republication (owner session; D2-02 amended)¶
A wrong compatibility declaration or option mapping is never edited in place (RD §4.13):
- Preview. Read the publications that used the declaration and every generated version and mapped revision they produced; show what rolling back changes, per session and per accepted result.
- Commit. Create a corrected question version (same content, corrected declaration and mapping), publish a new form version through the ordinary flow, and generate versions that restore the pre-mapping values or apply the corrected treatment, each attributed to the correction.
- History. The wrong declaration stays in history with a "corrected by" link; accepted results built on affected answers follow the explicit reconsideration rule; nothing is deleted.
Fixture: FX-VM-50.
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.
Owner session (5 October 2026): "a dedup merge or split aliases the study" reads "a duplicate
consolidation or reversal" (C21; the consolidated Study gets its own task once it has a qualifying
candidate and tasks on inputs freeze); a generated version drifts only the questions whose pinned
revision or class changed, or a candidate that stopped qualifying (under requireReanswer through the
generated incomplete version); a contribution exclusion drifts the task and leaves existing results
flagged until explicit reconsideration.
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). Owner session: D2-09 is the one owner decision still open after the
session (T-OI-01); the recommendation is unchanged and FX-VM-45 stays parameterised.
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).
Owner session (5 October 2026). The ProfileAdjudication record becomes the AdjudicationTask
aggregate, one per Study × profile × trigger (decision tie, unresolved Unsure, reason disagreement,
sole-source AI-model Unsure), holding immutable AdjudicationVersions (decision, reasons and
must-agree answers, rationale when the profile requires it, the input vector, the individual
adjudicator). The 25 September fallback after resolution is superseded by RS-R58: before an
adjudicator starts, a correction or new decision that satisfies the profile rule withdraws the open
task with a reason and one that still needs adjudication refreshes its input vector; after the
adjudicator starts, a changed input vector returns a typed InputsChanged at submit with the draft
kept; after resolution, nothing rewrites the adjudicated outcome. A later correction, contribution
exclusion, surplus decision or replaced external decision leaves the AdjudicationVersion as the
current final facet, flagged InputsChanged because its recorded input vector no longer equals the
current decisions, until an eligible adjudicator explicitly reconsiders it, which writes a new
AdjudicationVersion against the current vector. The candidate facet keeps recomputing and is shown
beside the flag. The actor is warned before commit at the level their access allows and the
adjudicator and assignee are informed (Q-27; O1 amendment; RS-R58). Adjudicator assignment is a
separate AdjudicatorAssignmentVersion (§16.5).
9.8 Accepted result versions under target and policy changes (owner session)¶
An AcceptedResultVersion (project, study, form, seq) is one immutable accepted result with
authority ∈ SingleAnnotator (system rule), HumanReconciled, Adjudicated (profile-owned screening
annotations), MergeResolved; it records its input set and candidate versions, the form version
(which pins standardTarget and ReconciliationPolicy), any override version, the effective target
used, the actor and the gold snapshot it produced (RS §3.2). Rules:
- A new target, policy or input creates a new version only through the entitled rule or person;
earlier versions stay immutable and queryable (RS-R09). Under
AutoAcceptthe configured rule is the entitled creator ofSingleAnnotatorversions (PROPOSAL, owner-visible in RS §12). - Raising the target invents nothing: results below it stay effective, labelled
BelowCurrentTarget, unless the publishing admin chose to suspend them (SuspendedByTarget, derived from the recorded treatment with no version written); readers that use accepted answers as current treat a suspended result as absent (RS-R10). - A publication that lowers the target to one or switches to
AutoAcceptcreates results only through the confirmed treatment, recorded with the operation and confirming admin (RS-R11; D2-01 amended). - Switching to
RequireHumanReconciliationkeeps existingSingleAnnotatorresults or queues their tasks; a queued result is replaced only when a reconciler completes (RS-R12). - Two or more qualifying candidates always need a human reconciler (
T-POL-03unapproved). - Derived standing (
Current,InputsChanged,BelowCurrentTarget,SuspendedByTarget,NeedsReReconciliation,Superseded) is never stored;NeedsReReconciliationkeeps §9.4's class rule.
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 frompmAnnotationHeadandpmAnnotationRevision. - 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_onlyis counted frompmSessionDraftby 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 andgoldNeedsReReconciliation(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.
- Owner session: previous-version exports include generated and merge-created versions with their
operation provenance; accepted values carry
AcceptedResultVersion.authorityand the form and override versions; adopted hints are labelled; manifests list the publisher's compatibility declarations and any guided corrections.
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). Owner session: Q-04 decided (a blank candidate comparison is "not assessed", Unknown and Not reported are answers); Q-16 and D4-12 are specialist inputs (T-SI-02); adopted hints (reconciledHintAdopted) are excluded from independent observations; human independent, human informed, external human and machine classes stay apart, and outputs on a model's training inputs are excluded by default (C22).
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. Owner session: snapshots also freeze the profile versions with their source policies, the model configuration versions, the filter versions behind pool history, the search documentation versions and the protocol record version they used (C12, C20, C22).
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. Owner session (5 October 2026; R4, OS-A14, OS-A15): R6 is universal baseline
conversion of every project after trials and pilots, faithful to legacy behaviour, with genuinely
missing history carried as legacy-gap states (NotRecordedInLegacy, CurrentSnapshotOnly,
UnknownLegacyAuthor, UnknownLegacyTime, UnknownAuthoredUnderDefinition, ValueOrDefaultUnknown,
LegacyCompletionUnvalidated) that are never answer values; versions, reviews, votes and
reconciliation results are never fabricated (BC §3.2, §3.4).
The table is amended in place and BC §3.4 is the full mapping.
| 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 |
| Stage target (owner session) | FormVersion.standardTarget in the immutable v1 form version, materialised from the stage's effective legacy target (SessionCountTarget, override or inherited); converging stages with different targets need the admin's choice; EnforceAnnotationTarget becomes a CapacityCap equal to the target only where legacy enforced it; stage idle timeout and in-progress limit become form-owned values |
The target is never an operational setting after conversion (superseded wording 3); never a guessed total |
| Single-reviewer studies on target-one forms (owner session) | Converted candidate evidence only; the v1 form version carries targetOneHandling = NoAcceptance (RS-R04a, PROPOSAL), so conversion creates no accepted result and no reconciliation work; exports keep "single annotator, not accepted" |
The admin moves the form to AutoAccept or RequireHumanReconciliation by publishing a new version whose treatment decides whether results are created (owner-visible ambiguity BC A1) |
| 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. Owner session: AuthoredUnder = Unknown carries the legacy-gap state UnknownAuthoredUnderDefinition; a missing or unresolvable author carries UnknownLegacyAuthor |
| 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. Owner session: current-only is named CurrentSnapshotOnly and "legacy-completed, unvalidated" is LegacyCompletionUnvalidated, counted as completed by default because legacy counted it; missing times carry UnknownLegacyTime |
| Reconciled answers | LegacyAuthorityUnknown snapshot, never a gold snapshot (Q-35) |
Removed by the owner session (5 October 2026; Q-35 removed): no legacy reconciliation records are assumed and no LegacyAuthorityUnknown authority exists; the conversion dry run counts any found and an unexpected record stops that project's case (BC-R20) |
| 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 |
pmSessionDraftChange (owner session) |
{sessionId, draftVersion} unique |
{projectId, sessionId, at} |
append-only draft change log (§7.7) |
pmDesignDraft, pmDesignDraftChange (owner session) |
_id; {draftId, draftRevision} unique |
{projectId, scopeRef, status} |
append-only changes (§3.9) |
pmStudyTargetOverride (owner session) |
deterministic override ID from {projectId, studyId, formId}; {overrideId, seq} unique |
{projectId, formId} |
append-only (§4.7) |
pmStudyVersion (owner session) |
{studyId, seq} unique |
append-only (§16.1) | |
pmAcceptedResultVersion (owner session) |
{projectId, studyId, formId, seq} unique; {projectId, commandId} unique |
append-only, or embedded in the task per the storage ADR (§9.8) | |
pmAIScreeningModelConfiguration (owner session) |
{projectId, configurationId, seq} unique |
immutable versions; head pointer for new references only (§16.3) | |
pmExternalScreeningDecision (owner session) |
{projectId, profileId, sourceKey, studyId, seq} unique |
current-pointer index on the decision key | §16.3 |
pmTrainingReferenceVersion, pmTrainingPolicyVersion (owner session) |
{projectId, referenceId, seq} and {projectId, policyId, seq} unique |
append-only (§16.4) |
Owner-session rows are PROPOSALs for the F1a storage ADR; a distinct domain record does not by itself
justify a collection (consolidation §1), so the ADR may embed where an append-only repository can still
enforce insert-only writes.
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 |
| I7 (owner session) | Every PublicationGenerated version references an existing publication operation and generation and an existing published form version; at most one exists per session, operation and generation; every mapped revision references its source revision and mapping |
| I8 (owner session) | Every StudyTargetOverride version references an existing Study and form in its project; every accepted result version references an existing form version and, where it records one, an existing override version; every profile version's source policy references an existing model configuration version in the same project |
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¶
5 October 2026: the harvest map extends this table to all the existing question-management work and the related dormant PRs, checked against the owner-session model. Its §7 notes, row by row, what this table still holds and what changes; where they differ, the harvest map applies.
| 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). Superseded by the owner session (5 October 2026): Chris chose attributable generated versions (D2-01 amended); the objections are met by attributing each version to the operation and publisher while the reviewer stays effective author, by never overwriting a newer reviewer version, by generating only where §8.11 says so, by letting FV4 add generations without rewriting earlier ones, and by measuring the per-session cost under D2-10 (§8.11) |
| 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. Owner session (5 October 2026): rows amended in place are marked; FX-VM-46 to FX-VM-58 are added and run in the same suites (C4 and C5 at F2 for the generation table and draft rebase, which RD §10 requires the F2 freeze to include) and, where they cite them, in the C20 to C22 suites.
| 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. Owner session: where the publication generated a version for that session, the late Save is refused as stale and the draft is rebased (RD-AE18) |
| 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. Owner session (D2-01 amended): phase-1 time stays flat; phase 2 writes at most one generated version per affected session per generation and only for the treatments that generate one (§8.11); a doNothing-only publication still writes none |
| 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. Owner session (D2-02 amended): any edit of a committed declaration or mapping is refused, pinned or not; the declaration records the publisher and time; correction goes through guided rollback and republication (FX-VM-50; RD-AE14) |
| 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 (owner session: I1 to I8) |
| 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. Owner session (D2-08 decided): two tabs or devices editing different answers both apply; the same answer edited from a stale per-answer base yields a conflict with both values recoverable; the lease and take-over clauses apply only if the brief keeps that presentation; Save consumes the draft atomically and links its change log (RD-AE02) |
| 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. Owner session: Q-34 decided, no longer pending; originals are unchanged and a declared incompatible change cannot be mapped (RD-AE15) |
| 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). Owner session: Q-26 decided; the treatments are those of §5.5 and FX-VM-51 |
| 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. Rewritten by the owner session (5 October 2026): changing the standard target creates a new form version through the publication impact process, generates no session version and shows sufficiency, readiness, authority and override changes (RD-AE08); changing a compare setting, bulk acceptance, the timeout, the in-progress limit or the capacity cap creates no version and one audit entry; DP5 and profile routes are profile version content and change only by profile publication |
| 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; owner session: still parameterised, D2-09 open) |
| FX-VM-46 (owner session) | The generation table (§8.11): every treatment combination produces the expected generated version or none; each is attributed to the operation and publisher; none records an invalid Complete; a forced phase-2 crash replays idempotently; a barrier-forced race never overwrites a newer reviewer version (RD-AE16) |
| FX-VM-47 (owner session) | Draft change log and rebase: 200 autosaves on the 2,023-question form send only changed answers and the draft rebuilds byte for byte from its base and change log; after a publication or a Save in another tab, non-overlapping edits rebase, overlapping ones show both values and no edit is lost (RD-AE01, RD-AE17) |
| FX-VM-48 (owner session) | Target-only publications: lowering 2 → 1 under AutoAccept creates SingleAnnotator results only where exactly one qualifying candidate exists; Studies with several agreeing candidates are never auto-accepted; raising the target deletes no result and labels results "below current target" unless suspension was chosen (RD-AE09, RS-R10, RS-R11) |
| FX-VM-49 (owner session) | Override treatment: publishing a new standard target lists every override with its treatment; no effective target changes without a recorded choice; each override change is a new immutable version and creates no form version; results record form and override versions (RD-AE10, RD-AE11) |
| FX-VM-50 (owner session) | Guided rollback: correcting a wrong declaration creates a corrected question version, a new form version and attributed restoring versions; the wrong declaration stays with a "corrected by" link; nothing is edited in place (RD §4.13) |
| FX-VM-51 (owner session) | Profile publication finds every mismatched decision, outcome and adjudication version; required new decisions stay pending; generated profile-session versions carry forward compatible decisions only under that treatment; frozen reports are unchanged (RD-AE22) |
| FX-VM-52 (owner session) | Setting classification (§4.6): a change to any inside-the-version setting (standard target, ReconciliationPolicy members, profile outcome settings) creates a version; a change to any operational setting creates one audit entry and no version; a step can hide but never reveal hints the form hides |
| FX-VM-53 (owner session) | Design drafts: two drafts on one form; the second publication is refused while the first runs, then rebases and recomputes its impact; a stale-base edit keeps the local value beside the current one; every change records actor, time and base revision (RD-AE20, RD-AE21) |
| FX-VM-54 (owner session) | AI model configuration versions: after a configuration moves to version 2, decisions accepted under version 1 still reference version 1; a source-policy change creates a new profile version through the Q-26 flow (RI-AE22, RI-AE23) |
| FX-VM-55 (owner session) | Training references and policies: editing a reference or policy publishes a new version; attempts in progress keep their pinned versions; no training record appears among live sessions, results or PRISMA inputs (TI-AE01, TI-AE02) |
| FX-VM-56 (owner session) | Stage study filter versions: a settings version that changes only steps keeps its filter version; a new filter version starts a sweep whose effective time is the activation stamp; publishing stage settings changes no session pin (SP-AE09, SP-AE13) |
| FX-VM-57 (owner session) | Study versions: a bibliographic edit, a merge and an unmerge each append a StudyVersion; references are byte-unchanged; field provenance is complete (DM-AE15) |
| FX-VM-58 (owner session) | Outdated-answer completion: under WarnAllowComplete Complete proceeds after the warning; under BlockUntilAddressed it is refused with the list; mandatory re-answering blocks in both; a session pinned to an older form version keeps that version's mode until it upgrades (RS-R39 to RS-R42) |
15. Decisions needed and engineering items¶
15.1 Decisions for Chris¶
All cited from the resolution brief's Batch D; none is minted here. Owner-session statuses (5 October 2026) are added to each row; the vocabulary is Decided, Decided-amended, Replaced, Removed, Deferred, Brief item, Specialist input, Engineering contract (no owner question) and Open owner decision.
| 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". Decided-amended: publication may create attributable generated session versions (§8.11); SF6 applies literally | yes (superseded by the owner's amendment) |
| D2-02 | Compatibility declared at commit, immutable once pinned; FV4 never changes it. Decided-amended: the publisher declares compatibility and mapping explicitly; immutable from commit; guided rollback and republication (§3.5, §8.13) | yes (amended) |
| D2-03 | Data type and multiplicity edits refused on published questions (today's behaviour). Engineering contract (no owner question): the recommendation stands as a PROPOSAL frozen at F1a (RD-R30) |
incompatible version of the same identity |
| D2-04 | A stage binds the form identity; the live route presents the session's resolved version. Engineering contract (no owner question): stands; targets never come from stages (RD-R31) | yes |
| D2-05 | Target, compare settings, gold completeness, guidance, DP5, routes, allocation, batches, expiry outside requirement versions. Decided-amended (in part): the standard target is inside the form version; every other setting follows §4.6 (gold completeness, DP5 and routes move inside versions as PROPOSALs) |
yes (amended) |
| D2-06 | System questions as data; adoption only through a form publication. Engineering contract (no owner question): stands (RD-R32) | yes |
| D2-07 | Draft and the reviewer's place: the recommended middle ground (§7.6). Decided: form-owned inactivity timeout and per-reviewer in-progress limit; one place across tabs and routes; expiry keeps the draft (§7.7) | middle ground (superseded) |
| D2-08 | Second tab read-only with take-over; conflict copy. Decided: one shared session and place across tabs and devices; connections tracked separately; base-version checks; the take-over or read-only presentation is a brief detail (§7.7) | yes (amended) |
| D2-09 | Pilots avoid overlapping reconciled questions until answered. Open owner decision (T-OI-01), the only one left from the 89 |
second task may revise |
| D2-10 | Scoped pauses with limits; drafts kept. Brief item: tested publication active-work protection with generated versions; measured pause and operation limits (figures proposed, not approved) | yes |
| D2-11 | One active publication per form. Decided: many drafts; one publication operation at a time; final state recheck (§3.9) | yes |
| D2-16 | Form size ceiling in pins and bytes; the 2,023-question project stays legacy (VB-12's figure; UNVERIFIED here). Replaced: no size-based exclusion; the largest project is an acceptance case; limits engineered and measured to fit it | yes (superseded) |
| D3-17 | Optional capacity cap as a form setting separate from the target. Brief item (carry-forward alignment): form-owned baseline through every route, stricter route caps, off by default (proposed) | yes |
| D4-17 | FEAT-001 D54/D55 replaced by RE2's non-blocking warning; no enforcement levels. Decided-amended (O3): configurable WarnAllowComplete (default) or BlockUntilAddressed, inside the form version (RS-R39 to RS-R42) |
yes (amended) |
| D4-04 | Training rounds as a step kind that never versions evidence as a contribution (PH-33's disposition). Decided-amended (S4): training steps with versioned references and policies, scoring, manual assessment, retries and optional group admission; still never live evidence unless explicitly promoted (§16.4) | yes (extended) |
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). Owner session (5 October 2026): Q-34,
Q-26, Q-04 and Q-36 are decided; Q-29 is decided-amended (a task exists; SingleAnnotator or
one-candidate human reconciliation); Q-35 is removed; Q-16 is a specialist input (T-SI-02). The only
open owner decision this model still depends on is D2-09.
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). Owner session: the guard refuses any edit after commit; the publisher's declaration and mapping are recorded with actor and time; guided rollback (§8.13); design drafts with per-item base checks replace the pending-edit lease |
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). Owner session: plus the generation planner of §8.11 (which sessions get a generated version and what it holds), the draft rebase rule (§7.7) and contribution-exclusion awareness in qualification | 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). Owner session: AcceptedResultVersion with its standing derivation and target-change treatments (§9.8); the AdjudicationTask input-vector rules (RS-R58) |
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 |
16. Versioned records added by the owner session¶
Added on 5 October 2026. Every record follows §2's rules: stable identity, append-only versions with a
digest, pins by ID, derived states never stored as facts. Names and storage are PROPOSALs for the
F1a naming and storage ADRs. Implementation remains on hold.
16.1 Study parent and StudyVersion¶
- The Study parent is mutable:
state(CurrentorTombstoned) andcurrentVersionId, changed only by CAS (consolidation §2). Content lives in immutable **StudyVersion**s(studyId, seq): displayed bibliographic fields with per-field provenance ({value, origin, decidedBy, decidedAt, confirmation}; originInheritedFromReference,SelectedFromReference,ManualOverride,SystemRule),referenceLinks[]andsourceDocumentLinks[]. - A bibliographic edit, a merge and an unmerge each append a version; references never change. A P1
import writes version 1; a Study created before P1 gets version 1 on first need, labelled
CurrentSnapshotOnly(RD §3.2, DM §3.2). - Merges and unmerges are C21 operations; merge-created session versions are
MergeResolvedorMergeCarriedForward, unmerge-created onesUnmergeCarriedForward(§7.3). Fixture FX-VM-57.
16.2 Stage study filter versions¶
- A
StageStudyFilterVersionis(stageId, filterSeq), embedded in the stage settings version, with a clause tree of AND/OR groups over profile outcome clauses and accepted answer clauses (option identities, accepted question versions, optional authority restriction), the implicit base predicate, its dependency set and a digest (Q-15 replaced; SP §3.3). - Evaluation is three-valued and fails closed on Unknown; an accepted answer recorded under a question version the clause does not accept is Unknown. Option identities make clauses survive wording changes. A settings version that changes only steps keeps the filter version. Activation runs a pool history sweep (C20). Fixture FX-VM-56.
16.3 AI screening model configurations, source policies and external decisions¶
- An
AIScreeningModelConfiguration(project, configurationId, seq)holds immutable versions of the project's description of a model: name, provider, model version or artifact, intended use, label vocabulary and its mapping to Include, Exclude or Unsure, project-supplied thresholds, the training-set context and an explicit "not supplied" list. Every change creates a new attributable version; the head points to the current version for new references only (E3 metadata ownership; RI §3.11). - A
ScreeningSourcePolicyis part of the immutableScreeningProfileVersionand pins the exact configuration version, the role (ContributingVoteorSoleScreener), scope and mapping. A policy change is a new profile version through §5.5; earlier decisions keep the profile version they were accepted under (RI-R35). - An
ExternalScreeningDecision(project, profile, sourceKey, study, seq)is versioned per source: a rerun or correction creates a new version and only the latest accepted version is current, so a source never becomes extra voters (RI-R37). AnExternalScreeningRunis idempotent by source, run ID and file digest. Contract C22. Fixture FX-VM-54.
16.4 Training reference and policy versions¶
- A
TrainingReferenceVersion(project, referenceId, seq)holds practice items (project Studies) and expected answers or decisions pinned to exact form, profile and question versions; it may be seeded by copying an accepted result or outcome, recording the source version without staying linked (S4; TI §3.2). - A
TrainingPolicyVersion(project, policyId, seq)holds the scoring rubric, pass criteria, assessment mode, feedback disclosure, retry rules, admission and whether promotion is allowed. Numbers are per policy and not approved platform defaults (TI §3.3). - A training step binds exact form and profile versions and one version of each; attempts pin the
versions current when they start; edits publish new versions and never change an attempt in progress.
Training sessions use author scope
training(attemptId). Training records are never accepted results, gold, targets, live statistics or PRISMA inputs; promotion is an explicit operation creating new live versions withpromotedFrom. Fixture FX-VM-55.
16.5 Other versioned records¶
| Record | Identity and versions | Rule |
|---|---|---|
AcceptedResultVersion |
(project, study, form, seq) |
§9.8 |
AdjudicatorAssignmentVersion |
Per profile (optionally per trigger), with actor and time | Versioned and audited; no profile publication; never grants authority (RS §3.9) |
ClassificationRuleVersion |
(project, ruleId, seq) over ProjectRule |
Immutable; changes supersede dependent InferenceResults, which keep history (TI §3.8, §3.9) |
CatalogueItemVersion |
(catalogueId, itemId, seq) |
Immutable; copies record copiedFrom and never change with the catalogue (§4.5) |
NotificationContentPolicy |
(project, version) with a current pointer |
Immutable versions with actor and time; read at send time; can only narrow disclosure (ACD §3.5) |
| Inference beta setting | Project operational setting with audit history | Off by default; explicit project-designer opt-in; never enabled by conversion (TI §3.7) |
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. Superseded by the owner session (5 October 2026), see §3.9, §7.7 and §16.4: draft change log, collaborative design drafts, training records |
| 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 |
| VA-03, DD-10 | Corrected (round 2) | §7.3, §8.1, §8.4, §13 | Superseded by the owner session (5 October 2026): D2-01 amended; generated versions per §8.11 |
Owner-session amendments record¶
Amendments from the specifications' §13 lists (5 October 2026) and where this page applies them. Planning only; implementation remains on hold.
| Source | Change | Where |
|---|---|---|
| RD §13 and RD §3.15 | The rows of the D2-01 before and after table; §15.1 statuses for D2-01, D2-02, D2-05, D2-07, D2-08, D2-09, D2-10, D2-11, D2-16; FX-VM-42 rewritten (a target change creates a form version); FX-VM-45 kept parameterised; fixtures for the generation table (FX-VM-46) and draft rebase (FX-VM-47); target inside the form version; explicit compatibility declaration immutable from commit with guided rollback; option mapping with new immutable revisions; setting classification; override interaction; draft change log | Header; §1.1, §1.2; §2 table and rules 4 to 6; §3.2, §3.5, §3.8, §3.9; §4.1, §4.4, §4.6, §4.7; §6.3; §7.1 to §7.3, §7.5 to §7.7; §8.1, §8.4, §8.5, §8.7 to §8.13; §11; §12; §13; §14; §15 |
| RS §13 | §5.1: profile settings that change outcome derivation are versioned (tie policy by owner decision; the others by RS classification); §9.7 gains the triggers and RS-R58 | §5.1, §9.7, §9.8, §10.3 |
| SP §13 | §2 and §5.3: the stage settings version embeds the filter version; capacity, timeout and limit move to the form's operational settings | §2, §4.4, §4.6, §5.3, §16.2 |
| DM §13 | §9.3: "a dedup merge or split aliases the study" becomes "a duplicate consolidation or reversal" | §9.3, §16.1 |
| BC §13 | §11: current-only → CurrentSnapshotOnly; "legacy-completed, unvalidated" → LegacyCompletionUnvalidated; AuthoredUnder = Unknown tied to UnknownAuthoredUnderDefinition; the reconciled-answers row removed (Q-35); the stage-target row added |
§11 |
| Phase-2 brief (screening profile versions and mismatch preview; training reference versions; AI model configuration versions) | Profile publication with mismatch detection; training and AI configuration version kinds | §5.5, §16.3, §16.4, FX-VM-51, FX-VM-54, FX-VM-55 |
| UX §13 | Status words "Draft auto-saved" and "Version checkpoint saved" (illustrative) | §7.7 |
Open items carried from this page: D2-09 stays open (T-OI-01); whether autoUpdate generates a
version is a brief confirmation (§8.11); the override treatment set, the setting classification beyond
the target and the rebase mechanics are PROPOSALs for the brief; every numeric limit is proposed,
not approved.