Versioning model¶
Temporary planning document; planning only. This is the one rulebook for versioning in the integrated review programme. It answers the round-2 reviews VA and VB and the versioning findings of the DC, DD, PH, SR, MS and V2 reviews. Contracts C2, C3, C4, C5, C9 and C11 in contracts are amended to match it. Those amendments, and this model's changes to the integrated plan (R2a–R2d, R4a), open questions (the rewritten engineering rows, E36–E45, A-14, A-25 and A-26) and acceptance criteria (release criteria and the §7.5 conformance tests), have been merged into those documents. Mechanics the model relies on but does not own (transactions, fences, the command ledger, ownership markers, durable effects, ordering and the Study canonical summary) live in the consistency model. Where this document needs one of them it states the versioning rule and links to the section that implements it.
Labels: OWNER (ledger ID or register §1.11 ID), RECOVERED (existing design documents or code,
not a new owner decision), PROPOSAL (this plan's recommendation), OPEN (Q-xx), ASSUMPTION
(A-xx), CODE-MAIN (verified on main at 0f5c61073; file and line cited). A rule with no label
is a PROPOSAL. Nothing here reopens a confirmed owner decision; where a decision has consequences,
the consequence is stated. Anything that needs Chris cites a Batch D question from the resolution
brief; this document mints no question IDs.
1. Purpose and status¶
1.1 What this document decides¶
The owner decisions fix the shape: forms own evidence and stages own workflow (SF1, SF2), the latest explicit Save or Complete is current (SL3), publication checks every prior version and records an admin choice (FV1–FV3), compatible same-context answers are shared across forms (SF3, SF5), gold is an immutable snapshot (GS1), and a published question is never deleted (QD1). The reviews found that the package used "compatibility" in four senses, keyed shared answers without a version component, modelled publication two incompatible ways, over-stuffed version containers with operational settings, and left options, entity instances, system questions, drafts and legacy duplicates without identity rules. This document decides each of those cells once.
The model in one paragraph: every definition and every piece of evidence has a stable identity and append-only immutable versions; every version, revision, session version and snapshot pins the exact versions it depends on by ID; a compatibility class derived from per-version declarations says which question versions mean the same thing; derived states (current, outdated, needs updating, qualifying, held, needs re-reconciliation) are computed from pins, classes and recorded policies and are never stored as facts; publication is a recorded policy, not a writer of evidence; and operational settings change with an audit trail and never trigger an impact flow. Time is transaction time only.
1.2 Status of each part¶
| Part | Status |
|---|---|
| Shared sessions, SL3, FV1–FV4, VU1–VU3, PS1–PS3, SF3–SF6, PV1, PV2, GS1, RE2, RE4, RE5, AG2, AG3, QY1–QY9, DP4, DP5, QD1 | OWNER; restated, never reopened |
Per-question requireReanswer / autoUpdate / doNothing; BreakingChange declared at version creation; breaking transitivity; FEAT-003's per-session categories; stored answer wording (Annotation.Question); the 25 September adjudication precedence rule |
RECOVERED (sources cited where used) |
| Compatibility algebra, class in the head key, option identity, requirement versus settings split, system questions as data, Upgrade, treatment vocabulary, per-answer state enum, entity-instance rules, storage rules, task held state, gold re-reconciliation | PROPOSAL; the parts that need Chris are listed in §15.1 with their Batch D IDs |
| Option mapping (Q-34), profile-version transition (Q-26), gold completeness (Q-04), target-1 forms (Q-29), legacy reconciled answers (Q-35), self-reconciliation (Q-36) | OPEN; this document says what holds until each is answered |
1.3 Code baseline¶
Code claims are verified on /home/chris/workspace/syrf/main at 0f5c61073 unless marked
UNVERIFIED. The files that matter here are
src/libs/project-management/SyRF.ProjectManagement.Core/Model/ProjectAggregate/AnnotationQuestion.cs,
OptionInfo.cs, Target.cs, Project.cs, StudyAggregate/Annotation.cs, AnnotationOptions.cs,
AnnotationSession.cs, ExtractionInfo.cs, src/libs/mongo/SyRF.Mongo.Common/MongoExtensions.cs
and MongoContext.cs, and src/services/web/src/app/shared/annotation/annotation-form-v2/annotation-form-v2-eligibility.ts.
2. Version kinds rulebook¶
One row per kind. "Identity" is what never changes. "Pinned by" says who references the exact version. "Immutable" says what may never be rewritten. "Derived" says what is computed on read and never stored as a fact (a materialised copy may exist only with its input-version vector, consistency model §8). "Impact flow" says whether a change can start the FV2/Q-26 admin flow.
| Kind | Identity | Pinned by | Immutable | Derived | Impact flow |
|---|---|---|---|---|---|
| Question definition | QuestionRef = {questionId, systemQuestionVersion (system questions only)} plus structural identity {definitionOwner (project or profile), parent QuestionRef, entityTypeId, repeatable} (§3.1) |
Versions, heads | Identity; a structural change is a new question | Status (draft, published, retired) from references (§3.8) | Never at commit; only through form or profile publication |
| Question content version | (QuestionRef, seq) |
Revisions; form, profile and system versions | Content once committed; compatibleWithPrevious once any revision pins it (§3.5) |
classSeq, class membership, system suggestion |
None by itself |
| Option | optionId inside its question |
Revision payloads (by ID); conditions and parent filters (by ID) | The ID; per-version content | Validity of answers that select it | Retirement or meaning change makes the version incompatible by default (§3.5) |
| Form requirement version | (formId, seq) | Session versions, tasks, stage settings versions, publication records | Everything once published: ordered pins with ancestors, requiredness, applicability graph, renderability result | Usage, impact categories | Publication starts the FV2 flow |
| Form settings | formId head | Nothing | Nothing; every change appends an audit entry (§4.4) | Sufficiency and readiness read the live value | Never |
| Profile criteria version | (profileId, seq) | Decision revisions, profile-owned answer revisions, stage settings versions | Everything once published: eligibility question pins, decision rules, must-agree set, requiredness | Decision standing under Q-26 | Profile publication starts the Q-26 flow |
| Profile settings | profileId head | Nothing | Nothing; audited (§5.1) | Routes and DP5 read live | Never |
| Stage settings version | (stageId, seq) | Revisions (PV1 provenance), session versions (route), tasks (route) | Bindings, steps, dependency edges, route policies, VS1, BL1, EW1 defaults, filter element | Active, from lifecycle status | Never for sessions; a new binding is an admission change (§5.4) |
| Stage lifecycle | stageId | Nothing | Status events are append-only | Active, readiness | Never |
| Session version | (sessionId, seq) | Tasks, gold (through revisions), exports, manifests | Kind (Save, Complete, Withdraw), pinned form version, route, full pin map, resolved question set, exposure state | Per-answer state, requirement standing, qualification (§7.4, §7.5) | Never |
| Draft | sessionId (one record; bounded conflict copies) | Nothing | Nothing; lease, etag, patches (§7.6) | Draft-changes flag; draft_only category |
Counted in the manifest, never transitioned |
| Revision | revisionId on a head | Session versions, gold snapshots, tasks, queries, policy records (mapping) | Everything; append-only | Current (relative to pins), outdated, valid under a version | Never |
| Head | AnswerContextKey including classSeq (§6.1) |
Session versions (through revisions) | The key; currentRevisionId changes by CAS only |
Current, outdated flags, conflicted | Never |
| Entity instance | Label head ID in the author's scope (§6.5) | Context keys (entity path), outcome cells, population membership | The ID; rename is a label revision; delete is withdrawal | Population membership attribute, order (presentation) | Never |
| Entity type | entityTypeId (system IDs minted at F1a, §3.1) | Question identity | The ID; legacy category string is a display alias | Capabilities (C1) | Never |
| Gold snapshot | (studyId, seq) | Queries, exports, PRISMA manifests | Entries (question context, reconciled revision ID), provenance | goldNeedsReReconciliation per entry (§9.4), pending-query flag |
Never |
| Reconciliation task input set | (taskId, seq) | Gold snapshot provenance | Pinned form version and candidate session versions per input set | Held per question, drift state (§9.2, §9.3) | Never |
| Screening outcome projection | (studyId, profileId) | Nothing | Nothing; rebuildable with its input-version vector | Entirely (consistency model §8) | Never |
| Publication policy record | (operationId, generation) | Nothing; read by derivation | Each generation once written; FV4 appends a generation (§8.7) | Every effect on sessions | This record is the impact flow |
| System question version | (systemGuid, systemQuestionVersion, seq) | Form versions, revisions | Everything once published by CAMARADES | Class, as for any question | None until a project form publishes a pin (§3.7, D2-06) |
| Template | (templateId, seq) | Nothing after copy; copies record the source (templateId, seq) | Everything once published | Nothing | Never; a copy never changes with its template (DP4) |
| Claim | Not a version kind. Claims are keyed by form or profile identity, never by version (brief §2.1, RT-11) |
Rules that hold for every kind:
- 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.
- 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.
- Operational settings never version. They change with an audit entry and are read live (D2-05).
- 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) |
| 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.
| Change between n−1 and n | Suggestion | Admin may |
|---|---|---|
| Wording, description, help, display labels, control type, option order, answer label, guidance | compatible | tighten to incompatible (a wording change that alters meaning) |
| Option added; option value or label renamed keeping the ID; parent filter changed; condition changed; validator loosened or tightened; response mode or metadata field added or loosened | compatible | tighten |
| Option retired; option replaced by a new ID (meaning changed); response mode removed; metadata field removed or made required | incompatible | loosen only with a one-to-one option mapping covering every retired option and no free-text change (Q-34 OPEN, SR-16) |
| Data type or selection multiplicity (D2-03 recommendation) | incompatible | never loosen |
| Parent, entity type, definition owner, repeatability | not a version; new identity | n/a |
Classes. class(1) = {1}; class(n) = class(n−1) ∪ {n} if compatibleWithPrevious(n), else
{n}. classSeq(n) is the lowest seq in class(n) and is stored on the version when the class is
settled. Compatibility is therefore an equivalence relation: two versions are compatible if and
only if they share classSeq, however far apart they are. This is exactly FEAT-001's transitivity
rule ("breaking if any skipped version was breaking", docs/features/annotation-versioning/README.md:360-368,
RECOVERED) restated as classes (PH-18).
Asymmetry is handled by validity, not by the class. "Options were only added" makes v2 compatible with v1, yet a v2 answer that selects the new option is not valid under v1. The class says the two versions may share a head and be compared; §3.6 says whether a particular answer is valid under a particular version.
How each decision uses it:
| Decision | Use |
|---|---|
| SF3, SF5 | Answers are shared only within a class (one head per class, §6.1); lineage across classes is shown read-only |
| AG3 | Agreement compares only within a class and flags differing versions inside it (§10.3) |
| FV3 | A completed session satisfies a changed question only when the pinned revision's class matches and the answer is valid (§7.5) |
| SR-16 | autoUpdate is allowed only within a class and only for valid answers (§8.2) |
| RE4, RE5 | A question is held when candidates' pinned revisions span classes (§9.2); prefill only within a class |
| GS1 | Gold whose class differs from the form's current pin is flagged for re-reconciliation (§9.4) |
3.6 Per-answer validity¶
valid(r, v) for a revision r and a question version v of the same QuestionRef holds when:
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) |
| Question version | any published form or profile version pins it | not discardable (QD1) |
| Form or profile requirement version | it is published | immutable; A-14's "a draft form can change until first use" means an unpublished requirement version. Preview (the AF2 admin host) creates no session |
| Form or profile requirement version | any FormSession (including draft-only), task or gold entry references it | reported in usage evidence (PS1); never a mutability question, because published versions are already immutable |
| Stage settings version | it is bound by an Active stage | its bindings govern admission (§5.4) |
A reviewer session can only exist against a published requirement version, so the "draft-only session against a draft definition" case cannot arise; the research's warning against turning an unedited open into a persistent session is met by creating the session on the first autosave (§7.1).
3.9 Designer pending edits¶
Designer edits before commit are a per-definition pending record with the same lease, etag and take-over model as reviewer drafts (§7.6), so two admins editing one question get a typed conflict and presence is a hint, never a control (QM v2 D010 harvested; PH-33). No pending-edit trail is kept beyond the current record.
4. Forms¶
4.1 Requirement versions¶
An AnnotationFormVersion is (formId, seq), immutable once published, containing:
- the ordered pins: one
(QuestionRef, seq)per question, with every ancestor included automatically (FEAT-001 parent integrity,README.md:244,RECOVERED); at most one version per question identity (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).
Publishing a new requirement version is the only way to change any of this (FV1). Removing a question, reordering, changing requiredness or upgrading a pin all create a new version.
4.2 Composition validity¶
A requirement version is composable only if, for every pinned child version, every conditional-parent
reference and every option parent filter resolves to an active option ID in the pinned parent
version (VA-06). Today these are validated against the live parent (OptionInfo.cs:51-60,
Target.cs:160-194, CODE-MAIN), which is exactly what a version-pinned form cannot rely on. The
designer refuses composition with a typed error naming the child, the parent and the missing option;
the admin picks a compatible parent version or commits a new child version. The validated graph
(nodes = pinned versions; edges = conditions and filters by option ID) is stored in the version and is
what the E23 evaluator reads. Dependency cycles are refused as they are for steps.
4.3 Renderability¶
Canonical forms render on AF2 only; there is no AF1 fallback for canonical routes (VB-08). AF2's
stage-review host fails closed on supported categories, exactly one label question per mutable-unit
category, parent-closed hierarchies and ordinary roots without targets
(src/services/web/CLAUDE.md:353, CODE-MAIN), and its eligibility reads
projectDetails.annotationQuestions and stage.annotationQuestions
(annotation-form-v2-eligibility.ts:263-274, :295, CODE-MAIN; VB's citation of
annotation-form-data-source.ts:25, 748 for the stage-keyed question source is UNVERIFIED here).
Therefore:
- 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) |
| Optional capacity cap | D3-17 (off by default; separate from the target) |
| Reconciliation compare settings | C9 |
| Gold-completeness policy | Q-04 (OPEN) |
| Form-level guidance | A-15 |
| Bulk-approve flag | Q-11 (OPEN, after R4a) |
Sufficiency reads the current target, as SessionCountTarget does today. Changing the target is an
audited setting change under the FEAT-024 definition-rewrite fence (C8), not a publication.
4.5 Templates¶
A template version is (templateId, seq). Instantiation copies question versions into the
project (or profile) as new identities at seq = 1, recording copiedFrom = (templateId, seq,
QuestionRef). A later template change never changes a copy (DP4, SET1; ownership D2-15).
5. Screening profiles and stage settings¶
5.1 Profile criteria versions and profile settings¶
The same split applies (VA-19). A ScreeningProfileVersion is (profileId, seq) and holds the
scientific requirement: eligibility question pins with ancestors (profile-owned questions, DP4),
decision rules (which answers derive Include or Exclude, DP3), the must-agree supporting-answer set
(RX1) and requiredness. It is immutable once published, pinned by decision revisions and profile-owned
answer revisions, and published through the Q-26 flow (OPEN; until answered, a profile version can
be published only before any decision exists under the profile).
Profile settings live on the profile head with audit history and no impact flow: the exclusion-reason reconciliation toggle (DP5; Off keeps recorded reasons and history), agreement and resolution routes (RX1), rationale settings (Q-32), the Unsure/Maybe and discussion-route options if D4-01 and D4-02 are approved, and the reference to the project's PRISMA phase mapping version (C12; the mapping itself is project-level per DD-20).
5.2 Decision heads and profile classes¶
A profile criteria version carries compatibleWithPrevious like a question version. Suggestion:
decision-rule change or must-agree change that adds a requirement → incompatible; eligibility question
changes inherit from the questions' own classes; everything else → compatible.
Decision heads (kind ScreeningDecision) are keyed {project, study, authorScope, profileId} with
no question and no class (§6.1). Unlike a shared answer, a decision has exactly one requirement
owner (its profile), and two active stages never present different versions of one profile at the
same time (§5.4), so the branching that VA-02 found for answers cannot occur. The profile's class is
used only to derive a decision's standing under the recorded Q-26 policy (pinnedOlderVersion,
needsRenewal) and to decide which cast decisions a collective outcome may read
(consistency model §8).
5.3 Stage settings versions and lifecycle¶
A StageSettingsVersion is (stageId, seq) on the Stage aggregate (brief §1.12) holding bindings
(form and profile identities with the version bound and when), steps, dependency edges, route
policies, VS1, BL1 and EW1 defaults and the filter element. It references the allocation regime and
batch plan by ID; allocation, batch, expiry, target enforcement, in-progress limit, idle timeout
and tracking settings are operational and live in their own records with audit (VA-08a; D2-05;
D3-18). PV1's "exact stage-settings version" stamp on a revision refers to this requirement-bearing
part only.
Lifecycle status is an append-only event list; Active is derived from status; Completed freezes the
bindings for display and readiness (RX2, RECOVERED from the v10 prototype lines cited in the
ledger) and refuses settings publication except through an approved change request (LC1).
5.4 PV2 binding meaning¶
PV2 says stage settings bind form and profile versions and historical work pins its requirements, and leaves the transition undecided. Two readings exist (VA-08b): a stage pins a version it presents (so two stages can present different versions of one form to the same session, which contradicts SF1's one session per study and form), or a stage binds the form and records which version it bound and when.
Recommendation (Batch D D2-04): the second. A stage binds the form (or profile) identity and records the bound version and time; the live route always presents the session's resolved version (its pinned version, or the current published version after Upgrade, §7.3); a Completed stage's frozen binding governs only historical display and readiness evaluation for that stage's reports. Consequences: publishing a form version is per form (FV2) and reaches every bound stage; binding a form to a new stage is an admission change for that stage, never a version transition for sessions; claims are keyed by form identity (RT-11).
6. Answers¶
6.1 Context key¶
AnswerContextKey (a shared-kernel value object frozen at F1a):
AnswerContextKey
projectId
studyId
authorScope candidate reviewer ID | reconciled | imported
definitionOwner project | profile:<profileId>
questionRef {questionId, systemQuestionVersion?}
classSeq the compatibility class of the pinned question version (§3.5)
entityPath[] instance IDs from the root (§6.5); empty for study-level questions
populationId default whole-study population ID derived deterministically from the study ID (DD-24)
classSeq is the change from the package's C2 (VA-02). One head exists per context and class. A
revision under a version of a different class creates a new head with the same questionRef and a
new classSeq; SF5 shows the older head's revisions as lineage, read-only. "Current", "contains
outdated annotations" (SF5, SF6) and Fix are then defined within a class, and the VA-02 scenario
(form F on Q v2 with requireReanswer, form G on Q v1 with doNothing) produces two heads that never
flag each other.
Consequence to note: when two forms pin different compatible versions of one question, they share
a head. An answer changed in F under v2 flags the reviewer's G session "contains outdated
annotations" (correct: it is the same answer, changed by the same reviewer), and if the v2 value uses
a v2-only option it is NeedsUpdatingValue in G's v1 context. The reviewer chooses whether to Fix
(SF5); the flag alone never removes qualification (SF6). The publication dialog lists every other form
that pins the question so admins can publish shared questions consistently (U6).
Decision heads omit questionRef, classSeq and entityPath; decision-owned answers add the owning
decision's head ID (§6.4). The research's per-kind natural keys
(../screening-specialised-annotation-research.md:675-682) are the source of these shapes.
6.2 Key hash and indexes¶
A unique compound index over entityPath[] is multikey, and MongoDB enforces uniqueness per array
element across documents, so heads for (Q, [cohortA, tp1]) and (Q, [cohortA, tp2]) would collide
on cohortA (VB-05; documented MongoDB behaviour, not exercised in this repository). Therefore:
- the head stores the key as an ordered value object and
contextKeyHash= SHA-256 over a versioned canonical serialisation (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).
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).
7.2 Session versions and pin maps¶
A FormSessionVersion is (sessionId, seq) with kind Save, Complete or Withdraw, the pinned
form version, the route (stage settings version, step), the accepted-snapshot-available fact and
exposure state (C3), commandId, HLC stamp and digest. It pins the complete map of
(headId → revisionId) for every answer in the session at that moment (VA-22), the shape FEAT-001's
ASV already had (README.md:296-303, RECOVERED). Storage may delta-encode behind the aggregate;
the logical contract, previous-version exports, candidate pinning, gold pins and E28's ceiling are all
defined on the full map. Each version also stores its resolved question set (the pinned versions
applicable in that session version, computed from the pin map and the form version's applicability
graph), immutable with the version and recomputable from it (PH-18).
7.3 Transitions¶
| Transition | Who | Writes | Rule |
|---|---|---|---|
| Autosave | reviewer | draft only | never a version, never Study (SL1) |
| Save | reviewer | revisions for changed answers, one incomplete session version | becomes current (SL3); removes completed qualification; consumes the draft (§7.6) |
| Complete | reviewer | as Save, kind Complete | validates every required applicable answer under the declared form version (§3.6); one qualifying contribution per reviewer, study and form (SF2) |
| Fix | reviewer, from an outdated flag | one incomplete session version pinned to the session's resolved version; opens the form (SF5) | explicit; the prior completed version stays history; may be combined with Upgrade |
| Upgrade | reviewer | one incomplete session version pinned to the current published form version with the same revision pins | offered from the Needs-updating banner, implied by Fix when the reviewer accepts it, or taken on the next Save or Complete that declares the current version; nothing is rebased because pins are unchanged; every Needs-updating mark then shows against the new version (VA-10) |
| Withdraw | reviewer or admin (settled in the C5 ADR) | one session version of kind Withdraw | append-only; the session no longer counts or qualifies; revisions stay readable; the hard delete reviewers have today never applies |
| Versioned clear | reviewer | withdrawal revisions for every head in the session plus a Save | replaces "Remove all annotations" (AC-R2a-09) |
A Save, Complete or Fix declares the form version it is based on. The server accepts exactly two
values: the session's currently pinned version, or the form's current published version (which is the
Upgrade). Any other declared version is refused with a typed StaleBase. A Save declaring a
superseded version after a publication is accepted pinned to that version and never rebased; the
recorded policy applies to it by derivation (§8.8). There is no publication-written transition: the
package's "policy transition (creates incomplete versions)" is deleted (VA-03, D2-01).
7.4 Per-answer state¶
Derived per pinned answer from (pinned revision, head current revision, pinned form version's class
for the question, current published form version's class, recorded policy, validity, applicability):
| State | Meaning | Copy (D3-03) |
|---|---|---|
Current |
Pinned revision is the head's current revision; class matches the required version; valid | none |
OutdatedOwnAnswer |
The same reviewer has a newer revision on this head (same class) that this session version does not pin | "Outdated answers" / "contains outdated annotations" (SF5) |
NeedsUpdatingVersion |
The required version's class differs from the pinned revision's class under requireReanswer, or the version is incompatible |
"Needs updating" (VU1) |
NeedsUpdatingValue |
Same class, but the pinned value is invalid under the required version (retired option, tightened validator, removed mode) | "Needs updating" |
NeedsAnswering |
Required, applicable and blank, including a newly added required question and a question newly visible after an ancestor change (FEAT-003 "new question" and "newly visible", RECOVERED) |
"Needs an answer" |
PinnedOlderVersion |
The session is pinned to an older form version under doNothing; no action required |
"Answered under an earlier version" |
NotApplicable |
Suppressed by an ancestor's answer or mode in this session's resolved graph; the revision is preserved | none |
Conflicted |
Legacy duplicates not yet resolved (§6.6) | "Conflicting earlier answers" |
VA-27 asked for six states; NeedsAnswering and Conflicted are added because FEAT-003 and VB-13
need them. U13 validates all eight.
7.5 Session effective state¶
Status is the owner's SL3 classification and is a stored fact of the latest explicit version:
draft_only, saved_incomplete, completed, withdrawn, plus the separate draft-changes flag.
Everything else is derived on read (D2-01):
- Requirement standing against the current published form version
fv_cur, given the latest explicit 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). Canonical readers (admission,
qualification, AF2, exports, tasks) derive this on read; query-path projections carry the
definition-version vector they were evaluated under and are rewritten by the publication operation
(consistency model §8).
7.6 Drafts¶
Rules only; mechanics are consistency model §4 and §5:
- One draft record per session, pinned to a base explicit version and base form version, stored as patches against the base, size-capped with E28. It holds a lease (holder = a stable client tab ID shared with tracking's connection model but working with tracking off, RT-10), an etag and a per-holder write sequence; a duplicate write sequence is success; a stale autosave arriving after a newer explicit version is rejected and discarded by the client.
- A non-holder's edits are kept as a bounded conflict copy (retained N days), so "keeps both" is literal; the second tab is read-only with "Take over editing", which transfers the lease (D2-08).
- Save and Complete present the draft etag and consume the draft atomically in the commit.
- No TTL on any draft; removal only by audited discard by the owner, an admin after revocation, or the system with an audit record; stale drafts are visible to admins because they block LC1.
- No autosave trail beyond the current draft and its conflict copies (PH-33): SL1's "draft changes/history" is satisfied because draft changes are preserved without explicit versions.
- A cross-form draft: a draft in form G based on an older revision of a head changed through form F gets a typed stale-base conflict at Save that shows both values and keeps G's draft (VB-15).
- Whether an autosaved draft holds the reviewer's place is D2-07; the recommended middle ground is held while the reviewer is active under today's idle and disconnect timers counting draft activity, released when they lapse with the draft kept, and Complete still allowed afterwards as an extra contribution (SF4's target is a minimum) unless an optional capacity cap applies (D3-17), in which case the reviewer is told honestly and may keep or discard the draft.
8. Publication¶
8.1 Two-step publication¶
Committing a question version has no session impact (§3.2). Publishing a form or profile version is the only impact point (FV2, Q-26). Publishing a system question version has no project impact until a project form pins it (§3.7). A publication records a policy and starts an operation; it writes no session versions and no answer revisions (D2-01).
8.2 Treatment vocabulary¶
The three recovered choices cover changed questions only; FV1's headline case (an added question) and removed questions had no treatment (VA-14). The per-question vocabulary is:
Change class (derived from the diff of fv_prev and fv_new) |
Treatments | Default |
|---|---|---|
added (new pin, required) |
countEarlierCompletes (earlier Completes satisfy v2 with the question blank; a missing answer is never manufactured, FV3) · requireAnswerBeforeCounting |
requireAnswerBeforeCounting |
added (new pin, optional) |
no choice; earlier Completes satisfy | |
removed (pin dropped, or inapplicable through an ancestor change) |
dropFromRequirement; answers stay in history and remain exportable with their version; children of a removed parent become NotApplicable |
|
changedCompatible (same class, higher version) |
autoUpdate (the pinned revision satisfies the new version iff valid, §3.6; no revision is written) · requireReanswer · doNothing |
autoUpdate |
changedIncompatible (different class) |
requireReanswer · doNothing |
requireReanswer |
mapped (incompatible by default, loosened with a Q-34 mapping) |
autoUpdate applies the mapping (§8.9) · requireReanswer · doNothing |
requireReanswer until Q-34 is answered |
| requiredness raised | treated as added for the counting choice |
autoUpdate is only offered within a class and only satisfies valid answers (SR-16, VA-01); a
many-to-one mapping or any free-text question change forces requireReanswer. The per-category part
of the policy says which session categories the treatments apply to (completed, saved-incomplete,
draft-only) and, for completed sessions left pinned under doNothing, whether they count toward
the new requirement (FV3's admin choice; no universal default is asked). The dialog is FEAT-003's
four-step flow (scope, compatible changes, incompatible changes, confirmation with per-question and
per-category counts; docs/features/question-management/README.md:266-298, RECOVERED) extended
with added and removed rows and the system suggestion (U6, PH-18).
8.3 Policy record¶
IssuePolicyRecord(operationId, generation) (embedded in FormVersionIssue in the domain model, with no collection of its own; the F1a naming ADR confirms the names and shape) holds: the form and the transition (fv_prev
versions → fv_new); per question {changeClass, treatment, mappingRef?}; per category {applies,
countingChoice}; the admin's rationale (SR-16); the admin, real actor and HLC stamp; the preview
digest confirmed; the usage evidence identity read at the fence (projection revision, source revision,
digest, or the authoritative-count basis under Q-31(b), MS-04). Generation 1 is written in phase 1;
FV4 appends generations (§8.7). The record is append-only and digested.
8.4 Derived effects¶
Every effect on sessions is the function in §7.5 evaluated against the policy record. Nothing is "applied" to a session. The same evaluator serves admission, qualification, readiness, AF2's banners and exports, so there is one truth and no fan-out. The query-path projections (Study canonical summary, FEAT-024 rows) are rewritten by phase 2 so legacy and pool readers converge; until the sweep reaches a study, admission and readiness for that form pause (D2-10) and fail closed on a stale projection (DC-08).
8.5 Phases¶
Summary only; the protocol is consistency model §4 and §7:
- Phase 1 is O(1). Fence the form (
Publishing), drain for at least the transaction lifetime plus the expired-transaction sweep plus a margin (D2-10, about 90 s at most), re-check the preview digest (re-confirm with the admin if it changed), then in one short transaction CAS the AnnotationForm head (currentPublishedSeq,publicationSeq), write the policy record (generation 1) and the operation record, and release the fence. No session is enumerated. - Phase 2 is an ADR-020-style operation (lease, generation fencing, chunks, predicate
pinnedFormVersionSeq < current ∧ appliedPolicyOp < 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).
8.6 Impact manifest¶
Built after phase 1 from authoritative records at the operation's HLC stamp, chunked, and used for the admin's summary, the notices and PS3 reproducibility; it is never an input to any effect. Its categories are FEAT-003's per-session categories expressed in this model's states (PH-18):
| Per session | Per question in the session |
|---|---|
category (completed, saved_incomplete, draft_only), prior form version, every stage that reaches the session, resulting standing under the policy |
carriedForward, carriedForwardBlank, autoUpdateSatisfied, needsUpdatingValue, needsUpdatingVersion, needsAnsweringNew, needsAnsweringNewlyVisible, removed, mappedByPolicy, conflicted |
Counts and identities come from pmFormSession, pmFormSessionVersion, pmAnnotationHead and
pmSessionDraft in one pinned snapshot. draft_only is counted authoritatively from pmSessionDraft
(indexed by base form version), never from the materialised usage family (MS-03); the manifest records
each count with its basis. The preview shown before phase 1 is the same computation with a digest;
phase 1 refuses if the digest moved.
8.7 Revision of a policy¶
FV4 (OWNER) lets the admin revise an unnecessary update requirement later with history. In this
model FV4 appends a policy generation to the same operation; it never rewrites versions, work,
drafts or the compatibility declaration (D2-02). The new generation is written by CAS on the policy
generation; a phase-2 batch in flight asserts the generation and re-sweeps. Derived states recompute
immediately for canonical readers; projections converge through the sweep. Supersession can only
loosen or tighten treatments and counting choices within the vocabulary of §8.2; it cannot make
autoUpdate available across classes.
8.8 Late Saves and Upgrade¶
A Save or Complete based on a superseded form version, whether it arrives during the drain, during
phase 2 or weeks later, is accepted pinned to the version its client declared and is never
rebased (§7.3). The recorded policy applies to it by derivation: under requireReanswer the session
is completed but not qualifying until the reviewer Upgrades and Completes; under doNothing it is
pinnedOlder and counts per the counting choice. The client shows the typed response ("saved under
v1; this form is now on v2") and offers Upgrade. Under doNothing the reviewer may keep working on
v1 indefinitely; Upgrade is always available and never forced (VA-10).
8.9 Option mapping as the only derived-revision writer¶
Q-34 (OPEN) asks whether QM v2's "map answers to updated options" is an approved form of
autoUpdate. Until answered, no mapping exists and nothing writes derived revisions. If approved as
recommended (explicit per-option mapping, one-to-one, meaning unchanged, old revisions untouched):
- the mapping
{oldOptionId → newOptionId}is declared with the question version as the ground for loosening its compatibility (§3.5) and referenced by the policy record; - the phase-2 operation writes, for every head whose pinned revision selects a mapped option, exactly
one successor revision with
authorship = policyDerived,provenance = {sourceRevisionId, mappingRef, operationId, generation}, the same real-actor fields as the source and the reviewer as effective author, idempotent per(headId, operationId); - derived revisions are excluded from SF5 outdated flags and from the "changed since" counts, are included in agreement as the reviewer's answer (meaning unchanged by definition), and are shown with a "mapped on publication" marker and the original value in history;
- the session's pin map moves to the derived revision on its next explicit version; until then §3.6 rule 3 treats the pinned revision as valid through its derived successor.
This is the single exception to rule 4 in §2 (brief §1.1). An alternative with no writer at all (apply the mapping on read) is recorded in §13; it was not chosen because every reader would need to know every mapping.
8.10 One active publication per form¶
At most one publication operation per form is active at a time, enforced by a unique partial index
on pmFormVersionIssue {formId} where the operation is active, the pattern
pmRobRunOperation.ActiveSearch already uses (MongoRobRunStore.cs:67-72, CODE-MAIN). A second
publish is refused with a typed PublicationInProgress (D2-11). FV4 is not a new publication; it is
a generation on the existing operation (§8.7). The same rule applies per profile.
9. Reconciliation and gold under versioning¶
9.1 Task identity and pins¶
The package keyed the task by study × form × version-compatibility class (a round-1 resolution of
B-28). That is wrong at form grain, because compatibility is per question, and it contradicts RE4's
one task per study and form (VA-04, DD-07). Correction: the task key is (projectId, studyId,
formId) with a deterministic _id. The task pins, as state, an input set (taskId, seq) holding
the form version it reconciles against and the full set of qualifying candidate session versions
(SF4: all of them). A new input set is appended when inputs change (§9.3); the gold snapshot records
which input set produced it. The reconciler's session is an entity of the task with
authorScope = reconciled, a current holder (X-RECLAIM, brief §2.1) and drafts keyed by the task.
9.2 Held questions¶
Per question of the task's form version, held is derived when the candidates' pinned revisions for
that question fall in different classes, or when a candidate's pinned value is invalid under the
task's form version, or when a candidate head is Conflicted. A held question blocks only itself
(prefill, agreement, the question's own final acceptance); the rest of the form reconciles. The
workspace shows the v10 held banner
(/home/chris/.codex/visualizations/2026/09/23/01a0cbec-0c32-7103-ab5a-bfc05665deb7/syrf-v10-review-2026-10-02/source/design_handoff_syrf_v10/RECONCILIATION.md:47-50)
with "Ask vN reviewers to update", which raises a Needs-updating request on those sessions (a C15
notice; the session's standing is unchanged by the request). v10's "Mark compatible" (RD12) is
replaced by the admin's compatibility declaration, which is immutable once pinned (D2-02); a held
question is released by the candidates' Upgrade, never by relabelling.
9.3 Drift triggers¶
A task moves to "inputs changed · re-check", never to retraction of gold, when: a new qualifying
candidate appears; a candidate Saves after Complete; a candidate Fixes or Upgrades after gold; the
form's current version changes by publication; a publication policy de-qualifies a pinned candidate
(requireReanswer, VA-11b); a candidate head becomes withdrawn; a dedup merge or split aliases the
study. Under doNothing with counting, candidates keep counting until they submit, which is v10's
"their last completed review keeps counting" (RECONCILIATION.md:49); under requireReanswer they
stop qualifying by derivation. The admin's publication choice selects which; the task never guesses.
9.4 Gold snapshots¶
A GoldSnapshot is (studyId, seq); entries are (question context without author, reconciled
revisionId); a new snapshot keeps unchanged references (GS1). Each reconciled revision pins a
question version, so each entry has a class. Derived per entry:
goldNeedsReReconciliation = classSeq(current form pin of q) ≠ classSeq(entry's revision) ∨
¬valid(entry's revision, current form pin) (VA-11a). Gold stays effective while flagged (QY1's
analogue), exports and PRISMA manifests label every gold value with its question version and class
(§10.2), and the task shows the flag; re-reconciliation produces a new snapshot. The pointer on
StudyGold moves by CAS (consistency model §4).
9.5 Shared question gold¶
Overlapping forms share question gold (RE4, OWNER). The package proposed "first publisher wins,
challenge only by query". Batch D D2-09 asks which: the recommended alternative is that the
second task sees existing shared gold prefilled as accepted, with its source snapshot and
reconciler shown, and the second reconciler may revise it in their own final submission, producing a
new snapshot with provenance {supersedesEntry, task, reconciler}; queries remain the route for
everyone else. Until D2-09 is answered, pilots use forms that do not overlap on reconciled questions
(A-19 holds until R2d anyway).
9.6 Queries¶
The query target is the reconciled revision ID (VA-23), so "one work item per accepted-answer version" (QY2) is one work item per revision, shared unchanged across any number of snapshots that pin it. QY9's "current applicability" compares the target revision with the snapshot's current revision for that head and with the form's current class for the question; a concern on a revision that gold no longer pins, or whose class is no longer current, is flagged for the assigned reviewer and never retargeted silently.
9.7 Screening adjudication¶
Adjudications are revisions on the reconciled-authority ScreeningDecision head, so they are
versioned by construction (V2-18); the ProfileAdjudication record in the domain model is the command
record with a generation, not a second store of the decision. Following Chris's 25 September
clarification, a submitted replacement of an input decision makes the earlier adjudication
inapplicable to the new input vector while keeping its history
(../screening-specialised-annotation-research.md:818-823, RECOVERED); the outcome projection
derives this (consistency model §8).
10. Statistics, agreement, exports and PRISMA with mixed versions¶
10.1 Usage¶
- Question-version usage is counted from revisions: distinct
(studyId, authorScope)with a revision pinned to(QuestionRef, seq), read 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.
10.3 Agreement¶
- Agreement compares only within a class, flags differing versions inside a class (AG3), never
compares across classes, and treats
Conflicted,Unknown-authored (§11) and held answers as "not comparable" with explicit counts. - Multi-select agreement needs identical option-ID sets; overlap is shown separately (AG2).
- Independent versus informed follows exposure (VS2): an accepted revision rendered into view, and the NS-06 kind "questioned in reconciliation", make later versions of that reviewer's session on that study × form informed. The agreement store is its own rebuildable store (D3-11).
- Statistical method and denominators are Q-16 and Q-04 (
OPEN).
10.4 PRISMA¶
PRISMA reads the collective authoritative outcome (PR1) from the outcome projection with its input-version vector; it never reads FEAT-024 rows (MS-11) and never changes because a form version, target or extra assessment changed. Report snapshots freeze the definition versions they were built from.
10.5 Transaction time only¶
As-of means "what SyRF knew at that commit stamp", not "what was true then" (VA-21). Order comes
from per-aggregate sequences and the HLC stamp; as-of(T) is offered only for T older than the
watermark (consistency model §11). observedAt on a
revision and legacy DateTimeCreated (settable, stamped at construction) are evidence fields with
trust levels and are never used for ordering or as-of selection.
11. Legacy adoption as v1¶
Rules for the R6 domain mapping (migration §3), which this document corrects where VB-13 showed it could not be applied:
| Legacy record | Canonical result | Rule |
|---|---|---|
| Project question | Identity (questionId kept, definitionOwner = project, entityTypeId from the category alias, parent kept) plus content version seq = 1, classSeq = 1, published |
Counts as published (QD1). Ancestors added to a form at adoption are display-only and never required (VB-13c) |
| Options | v0: optionId = OptionInfo.Id (OptionInfo.cs:114-131, CODE-MAIN); v1: IDs minted per value |
Unmatched values listed in the manifest with a disposition |
| Conditions and parent filters | v0 _v0OptionId → optionId; v1 and ADR-011 hybrid value sets → option IDs by value; boolean conditions kept |
Any value with no option is a manifest exception; the form version is not composable until resolved |
| System questions | Pin (systemGuid, Project.SystemQuestionVersion, seq 1) |
No structure is inferred from the current code; the seed for that systemQuestionVersion is the pinned definition |
| Stage question set | One initial form requirement version per stage set, bound to that stage; merging identical sets is an admin-reviewed choice | Never automatic |
| Answer | Head with the legacy annotation ID preserved through LegacyIdAlias, one legacySnapshot revision, AuthoredUnder typed union: Verified(v1) when Annotation.Question (Annotation.cs:41, AnnotationOptions.cs:30, CODE-MAIN) equals the adopted v1 wording, else Unknown (VA-18) |
Verified by comparison because the legacy upsert validates placement only for new questions (Project.cs:501-510, CODE-MAIN) and locks nothing, so wording can change after answers. Unknown revisions are excluded from same-version agreement and from exact-match prefill; they remain candidates with a coverage label. Option answers map by value to option IDs; an unmapped value is kept verbatim in an unmappedLegacyValue payload and is invalid under v1 |
| Conflicting duplicates for one context | One Conflicted head (§6.6) |
Never "latest wins" |
| Session | One current-only session version per legacy session with a full pin map of the adopted revisions; legacy-completed, unvalidated with the admin's count choice (E10); nullable timestamps kept |
Prior Save or Complete versions are never fabricated |
| Reconciled answers | LegacyAuthorityUnknown snapshot, never a gold snapshot (Q-35) |
|
| Everything with a legacy ID | LegacyIdAlias {projectId, legacyKind, legacyId, canonicalKind, canonicalId, manifestId}, unique on (projectId, legacyKind, legacyId) |
Used by #3944/#3945 remapping and exports (VB-13e); the alias table is itself append-only |
12. Storage and enforcement summary¶
Physical choices are the F1a storage ADR's (E15); this section fixes the rules the ADR must meet and the blueprint it starts from (VB improvement 1). Transactions, retries and the cache rule are consistency model §4 and §5.
12.1 Collections¶
Collection names are explicit and decoupled from class names (today they derive from the class
name, MongoContext.cs:154-163, CODE-MAIN, which is why QM v2's AnnotationQuestionV2 would land
in pmAnnotationQuestionV2); a test asserts the map. Names below are PROPOSAL for the F1a naming
ADR. Enums are stored as strings parsed from a closed set.
| Collection | Identity and unique keys | Other indexes | Notes |
|---|---|---|---|
pmQuestionDefinition |
_id record GUID; {projectId, questionId, systemQuestionVersion} unique |
{projectId, status} |
identity and status only |
pmQuestionDefinitionVersion |
{definitionId, seq} unique |
{projectId, questionId, classSeq} |
immutable; digest |
pmSystemQuestionVersion |
{systemGuid, systemQuestionVersion, seq} unique |
global; seeded idempotently | |
pmEntityType |
_id stable system IDs minted at F1a; {projectId, name} unique for project types |
||
pmAnnotationForm |
{projectId, formId} unique; head holds currentPublishedSeq, publicationSeq, settings, version |
phase-1 CAS target | |
pmAnnotationFormVersion |
{formId, seq} unique |
immutable; applicability graph; renderability result | |
pmDefinitionSettingsAudit |
append-only {ownerRef, seq} |
form, profile and stage operational settings changes | |
pmScreeningProfile, pmScreeningProfileVersion |
as for forms | ||
pmStageSettingsVersion |
{stageId, seq} unique |
on the Stage aggregate per brief §1.12 | |
pmFormSession |
deterministic _id; {projectId, studyId, formId, reviewerId} unique |
{projectId, formId, currentFormVersionSeq, status}; {projectId, studyId, reviewerId} |
publication predicate, usage, SF5 reads, candidates |
pmFormSessionVersion |
{sessionId, seq} unique; {projectId, commandId} unique (the command ledger entry) |
{projectId, formId, commitStamp} |
full pin map; resolved question set |
pmSessionDraft |
{sessionId} unique |
{projectId, formId, baseFormVersionSeq} |
conflict copies embedded, bounded |
pmSessionPresentation |
{sessionId} unique |
entity order and similar; never CAS-es the session | |
pmAnnotationHead |
deterministic _id; {projectId, contextKeyHash} unique; partial unique per kind |
{projectId, studyId, authorScope, questionId}; {projectId, questionId, currentQuestionVersionSeq} |
current payload copy |
pmAnnotationRevision |
_id client-proposed validated; {headId, seq} unique; {projectId, commandId, headId} unique |
{projectId, studyId, commitStamp} |
as-of reconstruction |
pmFormVersionIssue, pmFormVersionIssueChunk |
one active per form (unique partial index); chunks {operationId, chunk} unique |
phase 2; manifest chunks; IssuePolicyRecord generations embedded (generation unique within the issue; FV4), no collection of their own; the F1a naming ADR confirms the shape |
|
pmReconciliationTask |
deterministic _id; {projectId, studyId, formId} unique |
input sets embedded or {taskId, seq} |
|
pmStudyGold, pmGoldSnapshot |
{studyId} unique; {studyId, seq} unique |
pointer CAS | |
pmQueryWorkItem |
{projectId, reconciledRevisionId} unique |
R4b | |
pmLegacyIdAlias |
{projectId, legacyKind, legacyId} unique |
{canonicalId} |
append-only |
Constraints the ADR must keep: revisions are never embedded in sessions (SF3 and SF5 share them;
gold and tasks reference them); the full pin map lives in its own document per session version;
revisions stay in their own collection for the as-of index; Study holds only the canonical summary
(consistency model §8); every new pmStudy index goes
through the operator route (VB-18).
12.2 Append-only enforcement¶
The generic save is an upsert ReplaceOne filtered on Audit.Version
(MongoExtensions.cs:262-290, CODE-MAIN), so re-saving a loaded immutable record silently replaces
it (VB-10). Therefore: an AppendOnlyRecord base and an IAppendOnlyRepository<T> exposing only
Insert, InsertMany and Find; on DuplicateKey the repository compares digests and returns the existing
record idempotently or throws a typed conflict; an architecture test in the pattern of
StudyWriteLockArchitectureTests fails the build on ReplaceOne, Update*, Delete*, FindOneAnd*
or update models in bulk writes against a registered immutable collection; no TTL index on any
canonical collection; mutable heads (form, profile, session, head, task, gold pointer) are written
only through non-upsert CAS saves (#3985 prerequisite).
12.3 Identifiers and digests¶
- Deterministic IDs (SHA-256 over a versioned canonical key, stored as CSUUID, the #3944 precedent) for aggregates with natural keys: FormSession, AnnotationHead (from the key hash), ReconciliationTask, StudyGold, the default population, CanonicalOwnership. The natural-key unique index remains the real guard; a DuplicateKey means "reload and CAS".
- Client-proposed, server-validated IDs for revisions and entity instances: well-formed, unused, same project, generated per command; AF2 keeps its optimistic client IDs and never remaps temporary IDs across the store, comments, order and outcome cells (VB-16).
- Legacy IDs are kept at adoption through
LegacyIdAlias(§11). - Content digests (SHA-256 over a versioned canonical serialisation) on every definition version, revision, session version, policy record and snapshot; recorded in export manifests, so "two exports at the same watermark are identical" is a checksum comparison.
12.4 Referential invariants and the integrity checker¶
| ID | Invariant |
|---|---|
| I1 | Every revision references an existing question version (or system version) of the same QuestionRef as its head, in the head's class, with a payload shape the version defines; a policy-derived revision references an existing source revision, mapping and operation |
| I2 | Every session version references an existing published form version and existing revisions; every pinned head belongs to the session's project, study and author; (projectId, commandId) is unique |
| I3 | Every gold snapshot entry references an existing revision with authorScope = reconciled on the same study; StudyGold.currentSnapshotSeq exists; every query references a reconciled revision |
| I4 | Every stage settings version references existing published form and profile versions in the same project; every form version's pins reference existing question versions whose applicability graph resolves (§4.2) |
| I5 | Every task input set references existing candidate session versions on its study and form; every held question references an existing pin |
| I6 | No cross-project reference except system versions; every LegacyIdAlias target exists; every head's classSeq equals the class of its revisions' versions; no published definition is missing |
A read-only integrity checker ships in R2a, runs on seeds in CI, in E31 restore rehearsals, in R6
verification and after every restore; it reports zero findings on seeds and detects an injected
dangling reference per invariant. An architecture test lists every pm* collection with a
projectId against the deletion-lifecycle and restore registries (VB-11d).
12.5 Caching¶
Immutable definitions (question, system, form, profile and stage settings versions) are cached
process-wide by (versionId, digest) with no invalidation, and version-by-ID endpoints return
Cache-Control: immutable, which keeps AF2 history and Needs-updating reads off the Project document
(118–465 KB typical per .claude/rules/repository-cache.md). Canonical commands never take deciding
reads from the shared RepositoryCache
(consistency model §4).
12.6 QM v2 harvest and avoid for versioning¶
| Harvest (adapted to this model) | Avoid |
|---|---|
VersionHistory<T>; the typed AnnotationAnswer payload with EnsureCompatible (as the §3.6 validity check); ChildQuestionScope as the repeatable identity property; CandidateProjectQuestionSetValidator, CrossQuestionValidationService and AnnotationValidationState as E23 inputs; SystemQuestionFactory as the §3.7 seed builder; AnnotationMutationMapper, ExtractedAnnotationLegacyMapper, MigratedStudyReadModelAssembler and MigrationValidationService as R6 adapters and parity checks; the ExportSpec mode reservation renamed to form-version selectors |
ReconstructiveRollbackService and RevertToEmbeddedQuestionModel (destructive down-migrations); drafts and question-set versions inside the Project document; AQVersion.PublishDecisions, Optional and Multiple placement (requiredness belongs to the form; multiplicity is content but always incompatible); untyped AnswerOptionFilters; unbounded version arrays embedded in annotation and session documents; annotation identity without entity path, owner scope or population; stage-keyed export selectors; raw AsOfDate; ReplacementDraftLineagePlanner unless D2-03 keeps D38 |
13. Alternatives considered¶
| Alternative | Why not |
|---|---|
| Event sourcing (commands as the store, state by replay) | Commands already produce receipts and a total order (the command ledger plus HLC); nothing needs replay; projections here are rebuilt from immutable revisions and versions, not from events; the repository and team have no event-store tooling. The model keeps what event sourcing gives (append-only, derived projections) without a second write model |
| Bitemporal records (valid time plus transaction time) | Valid time is provenance only (observedAt, trust levels); no reader needs "what was true then" as a query dimension; as-of by transaction time plus coverage labels satisfies EX1 and EX2 (VA-21) |
| Copy-on-write session documents (a full session document per version) | Would embed revisions in sessions, which SF3 and SF5 forbid (shared revisions across forms) and which gold and tasks reference; document growth and the as-of index both suffer |
| Per-form snapshots with content-hash equality (freeze the whole form per publication; equal hash means compatible) | SF3 and DP4 need question-level identity and compatibility; hash equality is fragile under presentation edits and silent under meaning changes; the per-question class gives the same immutability with meaning preserved |
| Materialised policy-created session versions (DD-10's alternative: phase 2 writes real versions with provenance = policy) | Contradicts SL3 (a version no reviewer made becomes current), makes the publish operation an author of evidence on shared heads, makes FV4 resurrect or supersede them, and costs one version per affected session; derived standing gives the same answers at O(1) (brief §1.1, D2-01) |
| Apply option mappings on read (no derived revisions at all) | Simpler in storage, but every reader (exports, agreement, AF2, PRISMA) would have to know every mapping ever recorded; a single idempotent derived revision keeps readers ignorant of mappings (§8.9) |
| Class per form (the package's task key) | Compatibility is per question; a form version that changes five questions would split one study × form into several tasks against RE4 (§9.1) |
| Per-question versions with classes (chosen) | Transaction-time revision log with derived projections; satisfies every owner decision; fits MongoDB as SyRF uses it (append-only inserts, partial unique indexes, deterministic IDs, short transactions on a few documents) |
14. Conformance fixtures¶
Fixtures are versioned data run by the C2, C4, C5 and C9 suites at F1a and F4; the acceptance drafter maps them to criteria. IDs are provisional.
| ID | Fixture |
|---|---|
| FX-VM-01 | Compatible added option: the answer is shared by two forms with no flag |
| FX-VM-02 | A v2-only option is never offered under v1; a prior v2 value is rendered read-only with v2's labels by the Needs-updating presenter |
| FX-VM-03 | Two forms on incompatible versions of one question never flag each other (two heads) |
| FX-VM-04 | Fix shows the in-class current revision |
| FX-VM-05 | A late Save declaring v1 after v2 is published is accepted pinned to v1 and evaluated under the recorded policy; a Save declaring any other version is refused StaleBase |
| FX-VM-06 | Upgrade keeps every pin, shows Needs-updating marks against v2, rebases nothing |
| FX-VM-07 | Publishing a form with 10,000 sessions writes no session versions and no revisions; phase-1 time is flat across 1k, 10k and 100k sessions |
| FX-VM-08 | FV4 appends a policy generation; qualification is restored without touching versions or work; a batch in flight asserts the generation |
| FX-VM-09 | Renaming an option's value keeps answers valid; retiring it makes them NeedsUpdatingValue |
| FX-VM-10 | Composing a form that pins a child condition on an option absent from the pinned parent version is refused, naming the child, parent and option |
| FX-VM-11 | An incompatible publication flags affected gold entries for re-reconciliation; gold stays effective and is labelled with its version |
| FX-VM-12 | One task per study × form; one held question when candidates span classes; the rest reconciles; "Ask vN reviewers to update" raises a request and changes no standing |
| FX-VM-13 | A wide export with mixed versions carries per-cell version, class and option IDs and never mixes classes in a column |
| FX-VM-14 | Adopted answers are Verified(v1) when Annotation.Question equals the v1 wording, otherwise Unknown; Unknown is excluded from same-version agreement and prefill |
| FX-VM-15 | Three-version chain v1→v2 compatible, v2→v3 incompatible: classSeq(v1)=classSeq(v2)=1, classSeq(v3)=3; agreement v1/v2 flagged; v1/v3 never compared |
| FX-VM-16 | Flipping a compatibility flag after a revision pins the version is refused; flipping before that recomputes classes and re-validates active policies |
| FX-VM-17 | A data-type change creates an incompatible version of the same identity (pending D2-03); old revisions remain readable; a new head is created on first answer |
| FX-VM-18 | Heads differing only in the second entityPath element coexist; an exact duplicate is refused; the hash is stable across key schema versions |
| FX-VM-19 | Conflicting legacy duplicates adopt as a Conflicted head with no current pointer; excluded from prefill and agreement; resolved by the owner's Fix |
| FX-VM-20 | LegacyIdAlias remaps a #3944 thread reference and an export reference |
| FX-VM-21 | Unit delete is a withdrawal commit that flags the reviewer's other forms; rename keeps identity; duplicate mints new IDs with copiedFrom |
| FX-VM-22 | Suppressed descendants survive Save and Complete; exports resolve suppression |
| FX-VM-23 | Added required question: countEarlierCompletes keeps earlier Completes qualifying; requireAnswerBeforeCounting does not; no answer is manufactured |
| FX-VM-24 | Removed question: answers stay in history and exports; children of a removed parent are NotApplicable |
| FX-VM-25 | Deploying a new system-question version changes no published form and prompts no admin; the next form publication offers it |
| FX-VM-26 | v0 and v1 system error-type variants are distinct identities with distinct pins |
| FX-VM-27 | A session pinned to v1 renders v1 after v2 is published; a form AF2 cannot render is refused at publication; a canonical route never falls back to AF1 |
| FX-VM-28 | The append-only architecture test is green; digests verify on read-back; the collection-name map test passes; no TTL index exists on canonical collections |
| FX-VM-29 | The integrity checker reports zero findings on seeds and detects one injected violation per invariant I1 to I6 |
| FX-VM-30 | A previous-version export of a session equals its full pinned map |
| FX-VM-31 | All eight per-answer states render and are explained (U13) |
| FX-VM-32 | Two tabs: conflict copy kept; take-over transfers the lease; a stale autosave is rejected; a duplicate write sequence succeeds; Save consumes the draft atomically |
| FX-VM-33 | A draft in form G based on an older revision changed through form F gets a typed stale-base conflict showing both values and keeps G's draft |
| FX-VM-34 | A policy-derived mapping revision (pending Q-34) has provenance, is excluded from outdated flags, is written once per head and operation, and re-running the sweep writes nothing |
| FX-VM-35 | Question-version usage counts revisions; form-version usage counts session versions; draft_only counts pmSessionDraft; a shared session counts once across stages |
| FX-VM-36 | Agreement never crosses a class, flags differing versions within one, and separates informed contributions including "questioned in reconciliation" |
| FX-VM-37 | Two tabs' first autosaves create one FormSession; a client-proposed revision ID already used in another project is refused |
| FX-VM-38 | A second publish on a form with an active operation is refused PublicationInProgress; FV4 during phase 2 CAS-es the generation |
| FX-VM-39 | doNothing with and without counting yields pinnedOlderCounted and pinnedOlderNotCounted; the per-answer state is PinnedOlderVersion |
| FX-VM-40 | A profile criteria version with a rule change is incompatible; cast decisions take the standing the Q-26 policy records (parameterised until Q-26 is answered) |
| FX-VM-41 | Publishing a stage settings version changes no session pin and no qualification; a Completed stage's display is frozen (pending D2-04) |
| FX-VM-42 | Changing a target, compare setting, DP5 or route creates no version, no impact flow, and one audit entry |
| FX-VM-43 | The legacy category string resolves to the stable entity-type ID; enabling C1 changes no identity |
| FX-VM-44 | A query targets a reconciled revision ID; after a new snapshot that no longer pins it, or a class change, the concern is flagged for current-applicability review and not retargeted |
| FX-VM-45 | Shared gold in a second task is prefilled as accepted; a revision produces a new snapshot with provenance (parameterised until D2-09 is answered) |
15. Decisions needed and engineering items¶
15.1 Decisions for Chris¶
All cited from the resolution brief's Batch D; none is minted here.
| ID | What this document assumes until answered | Recommendation in the brief |
|---|---|---|
| D2-01 | Effects derived; projections rewritten by an operation; SF6 read as "changes the effective standing" | yes |
| D2-02 | Compatibility declared at commit, immutable once pinned; FV4 never changes it | yes |
| D2-03 | Data type and multiplicity edits refused on published questions (today's behaviour) | incompatible version of the same identity |
| D2-04 | A stage binds the form identity; the live route presents the session's resolved version | yes |
| D2-05 | Target, compare settings, gold completeness, guidance, DP5, routes, allocation, batches, expiry outside requirement versions | yes |
| D2-06 | System questions as data; adoption only through a form publication | yes |
| D2-07 | Draft and the reviewer's place: the recommended middle ground (§7.6) | middle ground |
| D2-08 | Second tab read-only with take-over; conflict copy | yes |
| D2-09 | Pilots avoid overlapping reconciled questions until answered | second task may revise |
| D2-10 | Scoped pauses with limits; drafts kept | yes |
| D2-11 | One active publication per form | yes |
| D2-16 | Form size ceiling in pins and bytes; the 2,023-question project stays legacy (VB-12's figure; UNVERIFIED here) | yes |
| D3-17 | Optional capacity cap as a form setting separate from the target | yes |
| D4-17 | FEAT-001 D54/D55 replaced by RE2's non-blocking warning; no enforcement levels | yes |
| D4-04 | Training rounds as a step kind that never versions evidence as a contribution (PH-33's disposition) | yes |
OPEN questions this model depends on: Q-34 (no mapping and no derived writer until answered), Q-26
(profile versions publishable only before any decision), Q-04 (gold completeness follows requiredness),
Q-29 (target-1 forms create no task), Q-35 (legacy reconciled answers never gold), Q-36 (no
self-reconciliation by default), Q-16 (agreement method).
15.2 Engineering items E36 to E45¶
| ID | What | Owner lane and contract | Freeze gate |
|---|---|---|---|
| E36 | Compatibility and class evaluator: diff classifier producing the system suggestion (§3.5 table), class derivation and classSeq stamping, the immutability guard (refuse a flip once pinned; re-validate active policies before that), and the designer pending-edit record with lease and etag (§3.9) |
L2 / C4 | F1a |
| E37 | Option identity and payload contract: optionId minting, the typed payload (value XOR mode, metadata, option IDs), display value and label resolution, and the v0/v1 legacy mapping with the manifest of unmatched values (§3.3, §11) |
L2 and L1 / C1, C4 | F1a (payload); R6 (mapping) |
| E38 | Composition and renderability validator: applicability graph against pinned parent versions, AF2 structural guards server-side with shared fixtures, typed refusals naming the question (§4.2, §4.3) | L2 and L5 / C4 | F1a |
| E39 | System question store: idempotent seeding into pmSystemQuestionVersion, (systemGuid, systemQuestionVersion) identity, the CAMARADES publication command, no per-read rebuild on canonical paths (§3.7) |
L2 / C4 | F1a |
| E40 | Session effective-state evaluator: per-answer state enum, requirement standing, qualification, policy composition across publications and FV4 generations, one implementation used by admission, readiness, AF2 and exports (§7.4, §7.5, §8.4) | L1 and L7 / C5 | F1a design; F2 |
| E41 | Head key value object, canonical serialisation and hash, partial unique indexes per kind, Conflicted state, AuthoredUnder union and LegacyIdAlias (§6.1, §6.2, §6.6, §11) |
L1 and L15 / C2 | F1a; R6 for the alias |
| E42 | Entity instance commands (create, rename, withdraw, duplicate), population membership as an instance attribute, outcome cells keyed by instance, presentation record outside versions (§6.5) | L1 / C2, C13 | F1a |
| E43 | Append-only persistence: AppendOnlyRecord, IAppendOnlyRepository<T>, explicit collection-name map with its test, content digests, the architecture test, no-TTL rule, string enums, and the integrity checker (§12.2 to §12.4) |
L1 / C1 | F1a; checker in R2a |
| E44 | AF2 VersionedAnnotationFormDataSource, the Needs-updating presenter contract (fromVersion, toVersion, treatment, reason, guidance, prior value rendered with fromVersion's labels), immutable-definition cache with Cache-Control: immutable, typed no-fallback error (§4.3, §12.5) |
L5 / C17 | F1c (seam), R2a |
| E45 | Reconciliation under versioning: task input-set versions, per-question held derivation, gold re-reconciliation derivation, second-task revision of shared gold (D2-09), query target identity (§9) | L6 / C9 | F4 |
15.3 Assumptions¶
| ID | Assumption | Basis | Cost if wrong |
|---|---|---|---|
| A-25 | Question versions form one linear sequence per identity (seq 1..n); branches never exist; a copy from a template or another profile is a new identity at seq 1 |
FEAT-001's sequential VersionNumber; DP4 copies |
Class derivation and classSeq need DAG rules; the designer needs merge semantics |
| A-26 | A requirement version pins at most one version of each question identity; two forms may pin different versions of one question only across forms, never within one | FEAT-001 QSV: one AQVersionRef per question |
Composition, the pin map and the head key need a per-pin version dimension |
Resolution record¶
| Finding | Category | Where | Note |
|---|---|---|---|
| VA-01 | Adopted | §3.5, §3.6 | One definition: declared at commit, immutable once pinned, classes as an equivalence relation with classSeq; validity separate; per-decision usage table; D2-02 |
| VA-02 | Adopted | §6.1 | classSeq in the head key; one head per context and class; the shared-compatible-version consequence stated |
| VA-03 | Corrected | §7.3, §7.5, §8.1, §8.4, §8.9 | Derived model chosen; "policy transition" deleted from C5; mapping is the only derived writer; D2-01 |
| VA-04 | Corrected | §9.1, §9.2 | Task key study × form (RE4 already decides); held per question |
| VA-05 | Adopted | §3.3, §11 | Stable optionId; answers store IDs; rename compatible, retire incompatible; adoption reuses OptionInfo.Id |
| VA-06 | Adopted | §4.2 | Composition validity against pinned parent versions; graph stored |
| VA-07 | Question | §4.4 | Requirement version versus form settings; D2-05 |
| VA-08 | Question | §5.3, §5.4 | (a) allocation, batches, expiry outside the stage settings version (D2-05); (b) binding meaning D2-04 |
| VA-09 | Question | §3.7 | System questions as data, (guid, SystemQuestionVersion) identity, opt-in adoption; D2-06 |
| VA-10 | Adopted | §7.3, §8.8 | Upgrade transition; late Save pinned to the declared version |
| VA-11 | Adopted; © Question | §9.3, §9.4, §9.5 | (a) gold re-reconciliation derived; (b) publication de-qualification joins drift triggers; © D2-09 |
| VA-12 | Adopted | §7.6 | One draft record with conflict copies (brief §1.8); D2-08 for the UX |
| VA-13 | Adopted | §3.8 | "In use" defined per container; published versions immutable regardless of sessions |
| VA-14 | Adopted | §8.2 | added, removed, changedCompatible, changedIncompatible, mapped with treatments and defaults |
| VA-15 | Adopted | §3.8 | Published, retired, removable from forms, discardable when unreferenced |
| VA-16 | Question | §3.1 | Both options presented; recommendation incompatible version; D2-03 |
| VA-17 | Adopted | §10.1, §10.2 | Per-cell version, class and option IDs; wide exports per form version or class; usage from revisions versus session versions |
| VA-18 | Adopted | §11 | Verified(v1) versus Unknown from Annotation.Question; code verified |
| VA-19 | Adopted | §5.1 | Profile criteria version versus profile settings; Q-26 applies to criteria versions only |
| VA-20 | Adopted | §2 table, §9.7 | Outcome is a rebuildable projection with its vector; mechanics in the consistency model |
| VA-21 | Adopted | §10.5 | Transaction time only; observedAt and legacy timestamps never order |
| VA-22 | Adopted | §7.2 | Full pin map per session version; storage may delta-encode |
| VA-23 | Adopted | §9.6 | Query target = reconciled revision ID; QY9 comparison defined |
| VA-24, VA-26 | Noted | — | No findings with these IDs exist in the verbatim VA report |
| VA-25 | Adopted | §3.1 | Entity-type ID is the structural property; category string is a display alias |
| VA-27 | Adopted | §7.4 | Eight per-answer states (six asked plus NeedsAnswering and Conflicted); session standing in §7.5 |
| VA improvement 1 | Adopted | §2 | The rulebook table |
| VA improvement 2 | Adopted | whole document | The simplest model that satisfies the ledger, with D2-03 as the identity choice |
| VA improvement 3 | Adopted | §8.1 | Two-step publication stated once |
| VA improvement 4 | Adopted | §7.5, §8.4, §9.2, §9.4 | Derive rather than store; PRISMA phase mapping stays project-level (DD-20), referenced from profile settings |
| VA improvement 5 | Adopted | §14 | Fixture set, extended |
| VA improvement 6 | Adopted | §11 | Wording evidence and OptionInfo.Id reuse |
| VA improvement 7 | Adopted | §8.2 | U6 shows the per-question vocabulary and suggestion |
| VA question 1 | Question | §3.5 | D2-02 |
| VA question 2 | Question | §3.1 | D2-03 |
| VA question 3 | Question | §8.4, §8.9 | D2-01 |
| VA question 4 | Question | §9.5 | D2-09 |
| VA question 5 | Question | §5.4 | D2-04 |
| VA question 6 | Question | §3.7 | D2-06 |
| VA question 7 | Question | §4.4, §5.1, §5.3 | D2-05 |
| VB-05 | Adopted | §6.2 | Key hash, scalar unique indexes, partial per kind; fixture FX-VM-18 |
| VB-06 | Adopted | §8.5, §8.8, §8.10 | Versioning rules stated (late Save pin, one active publication, FV4 generation CAS); protocol in the consistency model §4 and §7 |
| VB-08 | Adopted | §4.3, §12.5 | Versioned data source, Needs-updating presenter, renderability at publication, no AF1 fallback; one VB citation UNVERIFIED |
| VB-09 | Adopted | §6.5 | Instance identity, rename, delete as withdrawal, duplicate with provenance, population attribute |
| VB-10 | Adopted | §12.1, §12.2, §12.3 | Append-only repository and test, explicit names, digests, no TTL |
| VB-11 | Adopted | §3.7, §12.4 | Record GUID _id with unique natural key; global system store; I1 to I6; checker in R2a |
| VB-13 | Adopted | §6.6, §11 | Conflicted head, AuthoredUnder union, display-only ancestors, v0/v1 mapping, LegacyIdAlias |
| VB-15 | Adopted | §7.6 | No TTL, audited discard, patches with E28 cap, cross-form draft conflict fixture |
| VB-16 | Adopted | §12.3 | Deterministic IDs for natural keys; client-proposed validated IDs for revisions and instances |
| VB-17 | Adopted | §12.6 | Harvest and avoid table for versioning; AC-M0-04 wording goes to the acceptance drafter |
| VB improvement 1 (storage blueprint) | Adopted | §12.1 | Blueprint extended with policy records, settings audit, presentation, alias, entity type |
| VB improvement 8 (harvest/avoid) | Adopted | §12.6 | As above |
| DC-06 | Adopted | §8.5, §8.6 | Phase-1 O(1), drain, digest re-check, manifest after commit, predicate sweep; mechanics in the consistency model |
| DC-07 | Adopted | §7.6 | Brief §1.8 model restated as rules |
| DC-08 | Adopted | §7.5, §8.4, §9.7, §10.4 | Derived records carry their version vector; readers fail closed; mechanics in the consistency model §8 |
| DD-07 | Corrected | §7.1, §9.1 | Task keyed (study, form); versions as state; reconciler session an entity of the task |
| DD-10 | Corrected | §8.4, §13 | The two designs collapsed to derived effects per brief §1.1; DD-10's materialised alternative recorded and not chosen; D2-01 |
| DD-12 | Adopted | §3.1 | System entity-type IDs minted at F1a; O1 depends on them |
| DD-15 | Adopted | §7.1 | Deterministic SessionId; FormSession created on first autosave |
| DD-26 | Adopted | §3.1, §6.4 | definitionOwner versus owningParent; candidate child never attaches to a reconciled parent (C1 test) |
| PH-06 | Adopted | §3.4, §10.2 | Response modes and metadata in version content; suppressed answers preserved; exports resolve suppression; frozen versions settle the open question |
| PH-18 | Adopted | §3.5, §7.2, §8.2, §8.6 | Transitivity as classes; resolved question set stored per session version; FEAT-003 categories in the manifest and the four-step U6 flow |
| PH-33 | Adopted; disposition Question | §3.9, §7.6 | No autosave trail (brief §1.8); multi-admin editing via pending-edit leases; training rounds D4-04 |
| SR-16 | Adopted | §3.5, §8.2, §8.3, §10.2 | autoUpdate only within a class with valid values; one-to-one mappings only; rationale stored; qualificationPolicy and answeredUnderVersion exported |
| MS-03 | Adopted | §8.6, §10.1 | draft_only counted from pmSessionDraft; usage family over explicit versions only |
| V2-18 | Adopted; container PROPOSAL | §7.1, §7.6, §9.7 | Candidate key stays (study, form, reviewer); reconciler session on the task; adjudications are revisions; ReviewSession generalisation for screening-only steps at F3/F5 |