Read-only audit of #2572 at
af5136696and #2461 at1ca9f5def. Saved verbatim as evidence for the harvest map. Feature implementation is on hold; nothing was ported, rebased or closed.
Harvest audit: QM v2 PR-A domain foundation (#2572) and umbrella (#2461)¶
Read-only audit, 5 October 2026, against main at 7673ed0d3 and the planning package on
pr4061 (bc0c94734). Nothing was checked out, fetched, committed or commented. Path
abbreviations: Core/ = src/libs/project-management/SyRF.ProjectManagement.Core/,
QV/ = Core/Model/QuestionVersioning/, Tests/ = src/libs/project-management/SyRF.ProjectManagement.Core.Tests/.
SHAs: A = af5136696, #2461 = 1ca9f5def, main = 7673ed0d3.
1. Scope and state¶
1.1 #2572 (PR-A), head af5136696, base main¶
| Area | Files | Added lines | Content |
|---|---|---|---|
| Domain and services (C#) | 37 | 5,314 | QV/ (AnnotationQuestionV2, AQVersion, DraftQuestion and tree, DraftSnapshot, ProjectQuestionSet, StageQuestionSet, VersionHistory, value objects, ChildQuestionScope, MigrationStatus); extracted Annotation, AnnotationSession, OutcomeData aggregates; AnnotationAnswer; services (SystemQuestionFactory, QuestionPublishCandidateBuilder, CrossQuestionValidationService, candidate validators, rule codes, ReplacementDraftLineagePlanner, mutation mappers, SessionTransitionService stub); edits to Project.cs, Stage.cs, AnnotationQuestion.cs, AnnotationRelationshipValidator.cs; 7 class-map lines in ProjectRepository.cs |
| Tests | 19 | 3,887 | 169 new test methods under Tests/QuestionVersioning/ and Tests/Validation/, 2 BSON round trips, an API validator test edit |
| Docs and scripts | 11 | 1,082 | ADR-010 (renames), ADR-011 (two-level drafts), ADR-012 DRAFT (QuestionManagement entity), ubiquitous-language.md, glossary, scripts/apply-qm-renames.sh |
- No generated files, no API, no web, no migration, no repositories beyond the Project class map. The real size is the 67 files above (+10,283/−184). Nothing is wired to an endpoint.
- Relation to its base. Merge base with main is
b47f5c59c(25 April 2026). The branch holds the squash36190433d(17 April) plus 44 later commits (37 non-merge, 7 main merges), last commit 26 April. GitHub'supdatedAtof 1 September is metadata only. - Stack sync (verified). #2573, #2574 and #2575 contain
36190433dbut not4b5c6a8d32, the first later A commit.VersionHistory.csexists only at A's tip. B, C, D and #2461 carry the same blobs as36190433dforAnnotationQuestionV2.cs(0253500),Annotation.cs(69494e4) andSystemQuestionFactory.cs(b76cc0d); A's tip has6558874,7c744d9andda0b1e3. - Drift with main. 6,855 main commits since the merge base;
Project.cs57 commits (+506),Stage.cs23 (+397),AnnotationQuestion.cs8 (+99). A legacygit merge-treefinds 14 conflict hunks in 6 files (CLAUDE.md,docs/decisions/index.md,mkdocs.yml,Project.cs,Stage.cs,AnnotationRelationshipValidator.cs) and 3 modify/delete conflicts: main deleteddocs/features/advanced-screening-filtering/terminology.md,screening and filtering/screening-annotations-feature-spec.mdanduser-guide/glossary.mdin #2398 (14f2dd375, 4 May). All three ADR numbers are taken on main (ADR-010 S3 notifier, ADR-011 schema-v0 conditional parents, ADR-012 bulk PDF). - Semantic conflicts git does not flag (all three confirmed, §3). #2635, #2648, #2651.
1.2 #2461 (umbrella, draft, DO-NOT-MERGE), head 1ca9f5def¶
Against its merge base de8e312ad (24 April): 437 changed files plus 2 deletions, +77,083/−5,462,
309 commits from 20 March to 24 April (GitHub shows 738 files against today's main). A blob-by-blob
comparison of every changed path against the A, B, C and D tips and main:
| Result | Files | Detail |
|---|---|---|
| Byte-identical to a stack tip or main | 419 | 204 in C and D only, 145 in D only, 29 in B to D, 15 in A to D, 1 in A, C and D, 25 also or only on main |
| Different everywhere | 18 | 11 infra and config drift (syrf.sln, angular.json, Quartz files, checksums, mkdocs.yml, dependency map, app-config template); 5 docs/features/question-management/planning-archive/* files; older copies of m011-signalstore-integration-plan.md (2-line diff to D), annotation-impact.service.spec.ts (D adds session counts and fingerprint) and e2e/tests/session-lifecycle.spec.ts (main renamed to SlotReservations) |
The planning archive's decisions D001–D012 and knowledge K001–K019 are all present on main in
docs/planning/qm-v2-context/qm-v2-architecture-and-knowledge.md, with the 101-requirement tracker
beside it. #2461's domain code is the pre-A-tip squash level. Nothing in #2461 is of value that is
not already in #2572–#2575 or on main. It lacks every later A fix (§3 lists what those fixed).
1.3 Mapping to the RD specification and contracts¶
| RD / contract concept | Nearest A component | Gap that decides the verdict |
|---|---|---|
| Question definition and version (RD §3.4, VM §3.1–3.3, C4) | AnnotationQuestionV2 + embedded AQVersion list |
Versions embedded in the question; options keyed by value, no optionId; no condition field; DataType/AnswerArray frozen as identity (D2-03 makes them version content); Optional/Multiple on the version; no definitionOwner, entityTypeId or systemQuestionVersion identity |
| Compatibility classes (VM §3.5, D2-02 amended, RD-R20) | ChangeImpactClassification, advisory BreakingChange, PublishDecision on AQVersion |
No classSeq, no compatibility {suggestedBySyrf, declared, declaredBy, declaredAt, rationale}; the draft decision is mutable until publish and stored per publish, not immutable on the version |
| Option mapping (Q-34, RD-R21) | OptionMapping(OldValue, NewValue), ApplyTransition(Map) |
Value strings, no option IDs, no source-revision provenance |
Form and form version with standardTarget and ReconciliationPolicy (RD §3.5, OS-A12) |
ProjectQuestionSet + PQSVersion (one per project) and StageQuestionSet + SQSVersion (one per stage) |
No form entity; no target, requiredness, ancestor closure, applicability graph or renderability in any version; the target stays Stage.SessionCountTarget |
| Design drafts (RD §3.7, OS-A01, D2-11) | DraftContent on each question, DraftPQS, DraftSQS, DraftQuestion list on Project, GFS DraftSnapshot |
One mutable draft per item, overwritten by autosave; no base revision, change log, conflicts or presence |
| Session and session versions (RD §3.9, C5) | extracted AnnotationSession |
Keyed by stage with a random ID; embedded versions; no draft; kinds are triggers (ReviewerSubmit, AdminPublish, Reconciliation, Migration) |
| Answer heads and revisions (C1, C2, VM §6) | extracted Annotation |
One document per legacy annotation ID with embedded versions; no AnswerContextKey; locked to one session lineage |
| Publication-generated versions (D2-01 amended, RD §3.15) | TryApplyPublishTransition on Annotation, AnnotationSession, OutcomeData |
Writes versions without operation, generation or CAS attribution; copies status; clears answers on re-answer |
| System questions as versioned data (D2-06, RD-R32, VM §3.7) | SystemQuestionFactory |
Per-project documents with the system GUID as _id; seed text edited |
| Legacy-writer refusal on canonical scopes (C16, E49) | ThrowIfMigratedForLegacy* on Project and Stage |
Keyed by a per-project MigrationStatus, with a destructive rollback |
1.4 What the owner-session decisions change for A¶
- Target inside the form version (OS-A12, RD-R12). A has no form-level target. Harvested pieces
must hang off
AnnotationFormVersion, which gainsstandardTargetandReconciliationPolicyfrom the first canonical form (T-RD-01). A's stage-levelSessionCountTarget,EnforceAnnotationTarget,IdleSessionTimeoutMinutesandMaxInProgresspath is doubly superseded: main already has these fields, and D2-07 moves timeout and in-progress limit to the form. - Collaborative drafts (OS-A01). Every A draft type is a single mutable record. Only the
operations catalogue survives as reference for
DesignDraftChangeitem kinds. A's own ADR-011 anticipated an append-only autosave trail, but its GFS ageing contradicts "changes are never edited" (RD §3.7) and the reconstructable session draft log (RD §3.10). - Publication generates attributable session versions (D2-01 amended). This rehabilitates A's
idea that publication writes session versions (
AdminPublish), which the round-2 rule had rejected. A's implementation still breaks RD-R23 (status copied, answers cleared, no attribution or CAS), so it stays reference only. - Stage study filter replaces per-stage question sets (SP-R01, RD-R04, RD-R31).
StageQuestionSet,SQSVersion,StagePublishDecisionSummaryand theStageQuestionSetVersionReferencepinned on every annotation and session version have no target. A stage binds a form identity, and route provenance sits on session versions only. - Universal faithful conversion (OS-A14, OS-A15, BC-R01, BC-R27, BC-R29).
MigrationStatuswithRolledBackand re-migration assumes an opt-in, reversible per-project mode. BC has one engine, routing rollback only before the first canonical write, and forward recovery after it. The legacy-mutation guards survive as a pattern for theCanonicalScopeschecks. The mapping helpers must fail closed (BC-R08, BC-R26) instead of defaulting unknown types to strings.
1.5 Corrections to versioning-model §12.6¶
VersionHistory<T>gives no append-only guarantee. It wraps a mutable embeddedList, and BSON round trips or the genericReplaceOnestill rewrite it (§12.2). It fits only bounded embedded sequences such asIssuePolicyRecordgenerations, never revisions, session versions or draft changes.CandidateProjectQuestionSetValidatorandCrossQuestionValidationServiceare composition and content validation (VM §4.2, C4 "Composition and renderability"), not E23 applicability inputs. They validate against live or latest option values, so they need re-keying to pinned option IDs.AnnotationMutationMappermaps to A's aggregates, which have the wrong target shape. OnlyAnnotationAnswer.FromLegacyandAnnotationQuestionState's shape resolution are worth carrying into R6 adapters.ChildQuestionScopeis lazily committed at the firstMultiplepublish. As therepeatableidentity property it must be fixed at creation, and it covers only half of the shape (AnswerArraydecides multi-select).- Add to Avoid:
AnnotationAnswer.ApplyTransition; publish transitions that copy session status; single-session lineage on answers; per-stage question sets and stage-version pins on evidence; single mutable drafts and GFS snapshots; system seeds stored per project with the system GUID as_id; seed-text edits atseq 1; export projections built fromLatestVersion; A'sQuestionRefname, which means a version pin, whereas the package'sQuestionRefis an identity.
2. Harvest entries¶
| ID | PR | Component (type or file at SHA) | What it does | Verdict | Why (rule IDs, decisions) | Target (spec §, contract, release, tracker row) | Adaptation needed | Tests to carry | Effort | Evidence (path:line @ short SHA) |
|---|---|---|---|---|---|---|---|---|---|---|
| H-DOM-01 | #2572 | VersionHistory<T> |
Append facade over an embedded list: Latest, NextVersionNumber, Append |
Adapt | §12.6 harvest, narrowed by §12.1–12.2 (revisions, session versions and draft changes live in append-only collections; IAppendOnlyRepository) |
VM §8.3 policy generations inside FormVersionIssue; C4; F2; T-RD-04 |
Use only for bounded embedded sequences; enforce immutability in the repository and architecture test, not the facade | None exist; write facade tests | S | QV/VersionHistory.cs:18-58 @af51366 |
| H-DOM-02 | #2572 | AnnotationAnswer records, EnsureCompatible, FromLegacy |
Typed answer payload per value type and array shape; shape check; legacy-to-typed mapping | Adapt | VM §3.4 (value XOR responseModeId + metadata), §3.3 rule 2 (option IDs), §3.6 step 2; C1 revision payload; BC-R08, BC-R26 |
C1, C2; VM §6.3; F1a payload value object T-RD-01; R2a T-RD-02; adoption T-BC-01 | Option answers carry option IDs; add response mode, metadata and unmappedLegacyValue; FromLegacy and ResolveValueType fail closed on unknown types; structural equality for list payloads |
AnnotationAnswerTests: 3 EnsureCompatible_*, FromLegacy_*, 8 *_CopyAndClear / display |
M | Core/Model/AnnotationAggregate/AnnotationAnswer.cs:19-44,89-103 @af51366 |
| H-DOM-03 | #2572 | AnnotationAnswer.ApplyTransition, AnswerHandlingStrategy, OptionMapping(OldValue, NewValue) |
Keep copies, Map remaps strings, ReAnswer clears | Avoid | RD §3.15 (re-answer keeps pins and shows Needs updating), RD-R21 and Q-34 (new revision on the same head with {sourceRevisionId, mappingRef, operationId, generation}); null mapped to "" |
RD §3.15, §4.8; C4 publication command; T-RD-04, T-RD-05 | Not ported; mapping is rebuilt on option IDs as a revision writer | ApplyTransition_Map_* only as mapping fixture inputs |
— | AnnotationAnswer.cs:46-87 @af51366 |
| H-DOM-04 | #2572 | PublishDecision, DraftPublishDecision, ChangeImpactClassification, AQVersion.BreakingChange, StagePublishDecisionSummary |
Per-question impact decision drafted by autosave, promoted at publish, stored on the question version and per stage | Reference only | D2-02 amended and RD-R20 (immutable publisher declaration on the version); §12.6 avoids AQVersion.PublishDecisions; VM §8.2–8.3 (per-question treatment and per-category counting in IssuePolicyRecord) |
RD §3.4, §4.8; VM §3.5, §8.2; C4; F2 T-RD-04 | Vocabulary only: classification maps to the declaration and strategy to treatment; add added, removed and requiredness classes plus categories |
PromoteDraftPublishDecisions_* (3) as cases |
— | QV/VersioningValueObjects.cs:101-171 @af51366; QV/AQVersion.cs:65-68 |
| H-DOM-05 | #2572 | AnnotationQuestionV2, AQVersion, QuestionOptionV2, AnswerOptionFilter, DraftContent |
Published question aggregate with init-only structure, embedded version list and one pending draft | Reference only | VM §3.1–3.3, §12.1 (pmQuestionDefinitionVersion); D2-03 PROPOSAL; VB-17 (requiredness is form-owned); DD-12 (EntityTypeId); D2-15 amended (catalogue copiedFrom replaces Scope/OwnerProjectId); no condition field (§3, N1) |
RD §3.4; C4; R2a; T-RD-01, T-RD-02 | Keep the identity/content split, init-only identity, never-delete (QD1), StartDraftFromVersion as "seed a design draft from an older version"; rename A's QuestionRef |
QuestionVersioningTests publish, no-draft, independence cases as FX-VM ideas |
— | QV/AnnotationQuestionV2.cs:15-135,197-221,335-351 @af51366 |
| H-DOM-06 | #2572 | ChildQuestionScope, AnnotationCollectionMode, AnnotationQuestionState.ResolveCollectionMode/ResolveValueType |
Maps legacy AnswerArray/Multiple/DataType to single, multi-select array or repeated instances |
Adapt | VM §3.1 (repeatable = Multiple && !AnswerArray, identity), D2-03 (multiplicity is version content, always incompatible); C2 instance element in entityPath |
VM §3.1, §11; C2; T-BC-01 and T-BC-03 mapping; T-RD-01 identity | Fix repeatable at creation (no lazy commit); unknown DataType fails closed; one mapping table shared by BC and the designer |
ChildQuestionScopeTests (4); add a mapping-table test |
S | Core/Model/AnnotationAggregate/Annotation.cs:489-549 @af51366; QV/ChildQuestionScope.cs:28-35; QV/AnnotationQuestionV2.cs:80-99 |
| H-DOM-07 | #2572 | SystemQuestionFactory (18 definitions with per-SystemQuestionVersion parent and option resolvers) |
Builds system questions from code for a project | Adapt | D2-06, RD-R32, VM §3.7 rules 1–2 and §11 system row (seed for seq 1 is today's definition) |
pmSystemQuestionVersion seed; C4 system row; T-RD-01 (D2-06 contract), T-RD-02; parity input to T-BC-01 |
Write global records keyed (systemGuid, systemQuestionVersion, seq) with a structural digest, never per-project documents with the GUID as _id; restore main's exact text at seq 1 (A's double-space fix becomes a later version); mint stable option IDs (main keeps candidate GUIDs commented out); add a parity test against main's AnnotationQuestion.SystemQuestions (#2651 anchor split) |
SystemQuestionFactoryTests: GUIDs, v0/v1 error-type variants, cohort parents; drop the no-double-space test for seq 1 |
M | Core/Services/SystemQuestionFactory.cs:18-50,119-127,171-187 @af51366; main Core/Model/ProjectAggregate/AnnotationQuestion.cs:417-421,724,739 @7673ed0 |
| H-DOM-08 | #2572 | ProjectQuestionSet.Publish, PQSVersion |
Appends an ordered snapshot of (questionId, versionId) pins; refuses empty IDs and duplicates; copies the input |
Adapt | VM §4.1 (ordered pins, one version per question, A-26), FV1; OS-A12 | AnnotationFormVersion pins; RD §3.5; C4; R2a T-RD-02 |
Per form, not a project singleton; add requiredness, minimum instances, ancestor closure, applicability graph, renderability, standardTarget, ReconciliationPolicy; store in pmAnnotationFormVersion, not in Project |
Publish_ShouldAppendVersionWithRefs, Publish_ShouldSnapshotOrderedRefs_CallerMutationDoesNotAffectPublishedVersion, duplicate refusal |
S | QV/ProjectQuestionSet.cs:167-198 @af51366 |
| H-DOM-09 | #2572 | DraftPQS, DraftSQS, DraftQuestion, DraftQuestionTree, DraftContent, DraftSnapshot (GFS), Project and Stage class-map lines |
Single mutable designer drafts embedded in Project and each question, with ageing snapshots | Avoid | OS-A01 and D2-11 (many drafts, append-only DesignDraftChange with base revision, recoverable conflicts); RD §3.7 (changes never edited); §12.6 (no drafts in Project) |
RD §3.7, §4.7; VM §3.9; C4; T-RD-02 (single editor), T-RD-05 (collaborative) | Not ported; reuse the operation list (add, remove subtree, reorder, promote, withdraw, fork) as change item kinds | DraftQuestionTreeTests, ProjectDraftQuestionsTests as cases |
— | QV/DraftSnapshot.cs:9-84 @af51366; QV/AQVersion.cs:79-92; Core/Model/ProjectAggregate/Project.cs:109-218 |
| H-DOM-10 | #2572 | ADR-011 (A) two-level draft and formal versioning | Autosave trail vs explicit Save or Publish; session not mirrored | Reference only | Anticipates RD §3.10 and VM §7.7; GFS retention and per-annotation drafts conflict with them; number taken on main | F1a storage ADR rationale; T-RD-01 | Cite in the storage ADR; never merge | — | — | docs/decisions/ADR-011-qm-v2-two-level-draft-and-formal-versioning.md:44-110 @af51366 |
| H-DOM-11 | #2572 | ADR-010 renames, ubiquitous-language.md, scripts/apply-qm-renames.sh |
Rename table (39 rows; high-risk renames deferred) and glossary | Reference only | Superseded by domain-model §8 and the F1a naming ADR; ADR-010 number collides | domain-model §8; naming ADR; T-RD-01 | Use as an old-to-new name key when reading dormant code | — | — | docs/decisions/ADR-010-qm-v2-ubiquitous-language-renames.md:39-79 @af51366 |
| H-DOM-12 | #2572 | ADR-012 DRAFT QuestionManagement entity inside Project |
Keeps definitions in the Project aggregate with mode history and RevertToEmbeddedQuestionModel |
Avoid | domain-model §5 (questions leave Project); §12.6 and AC-M0-04 (no destructive rollback); BC-R27 | — | Verb taxonomy only, as input to domain-model §6.3 command names | — | — | docs/decisions/ADR-012-DRAFT-question-management-entity-extraction.md:34-60 @af51366 |
| H-DOM-13 | #2572 | CandidateProjectQuestionSetValidator, CandidateStageQuestionSetValidator, QuestionPublishCandidateBuilder, CrossQuestionValidationService, QuestionValidationRuleCodes (AQ001–AQ017) |
Publish-time content, option-filter, cycle, placement and stage-subset checks with typed codes | Adapt | VM §4.2 (resolve against the pinned parent version by option ID), §4.3; C4 composition; #2648 and #2651 (main's placement rules win) | VM §4.2–4.3; C4; F1a C4 part T-RD-01; R2a T-RD-02 | Build the candidate from a design draft plus pinned versions; re-key filters to option IDs; replace A's placement copy with main's AnnotationQuestionPlacementRules/Validator; turn the stage-subset rule into ancestor closure; namespace the codes against main's FL/NQ codes; keep FormVersionNotRenderable separate |
CrossQuestionValidationServiceTests (9), QuestionPublishCandidateBuilderTests (3), QuestionValidationRuleCodesTests (2) |
M | Core/Services/Validation/CandidateProjectQuestionSetValidator.cs:21-60,136-182,231-256 @af51366; Core/Services/QuestionValidationRuleCodes.cs:11-59 |
| H-DOM-14 | #2572 | AnnotationRelationshipValidator over AnnotationValidationState/OutcomeDataValidationState |
Decouples tree, conditional and lookup checks from embedded annotation classes | Adapt | Useful for BC dry-run findings (BC §4.2) and the R0 legacy floor; canonical E23 is option-ID and entity-path based (VM §3.6) | BC §4.2; C16; T-BC-01 | Rebase on main's validator, keeping #2635's DuplicateAnnotationId guard; populate ChildIds in FromExtracted |
AnnotationRelationshipValidatorProjectionTests (2); main's validator tests stay authoritative |
S | Core/Services/Validation/AnnotationRelationshipValidator.cs:43-60 @af51366; Core/Services/Validation/AnnotationValidationState.cs:32-42; main same validator :56 @7673ed0 |
| H-DOM-15 | #2572 | Extracted Annotation aggregate, AnnotationVersion, AnnotationStructure/SubmissionState/VersionState, AnnotationMutationMapper |
One document per answer with embedded versions, each pinning session, question and stage versions | Avoid | C1 and VM §6.3 (heads and revisions in separate collections); C2 (AnswerContextKey with author scope, definition owner, classSeq, entityPath, population); RD-R04 and RD-R31 (no stage pins on evidence); §12.6 and AC-M0-04 (no unbounded embedded arrays); no command ID, HLC or digest |
C1, C2; VM §6; R2a T-RD-02 | Not ported; keep the ideas of factory-fixed identity, a collection-mode invariant across versions (I1) and legacy ID preservation (LegacyIdAlias) |
AnnotationAggregateTests (12), ExtractedReviewVersioningTests (13) as C1-T ideas |
— | Core/Model/AnnotationAggregate/Annotation.cs:15-66,343-368,440-457 @af51366 |
| H-DOM-16 | #2572 | Extracted AnnotationSession, AnnotationSessionVersion, AnnotationSessionVersionTrigger |
Per-stage session; each version lists all annotation and outcome version references; status projected from the latest version | Reference only | Matches the full pin map (VM §7.2, C5 E28) and status as a fact of the latest explicit version (C5); conflicts with RD-R04 and C5 E27 (Study × form, deterministic ID), VM §7.1 (reconciler is not a FormSession), SL1 (no draft), RD-R23 | C5; RD §3.9; VM §7.1–7.3; T-RD-02, T-RD-04 | Pin-map shape and derived-status rule only | AnnotationSessionAggregateTests (10) as cases |
— | Core/Model/AnnotationSessionAggregate/AnnotationSession.cs:20-63,326-359 @af51366 |
| H-DOM-17 | #2572 | TryApplyPublishTransition (Annotation, AnnotationSession, OutcomeData), CreatePublishTransitionReplacement |
Appends a publish-triggered version unless the state is unchanged; can spawn a replacement annotation | Reference only | D2-01 amended and RD §3.15 (the generation table decides; CAS on the session head; deterministic ID per (sessionId, operationId, generation); publisher attribution; never an invalid Complete); replacement IDs are D38 lineage (RD-R30) |
RD §3.15, §4.8 phase 2; consistency §4.2; C4, C5; F2 T-RD-04 | Keep "no version when nothing changes" as the no-generation rows | — | — | Annotation.cs:270-341 @af51366; AnnotationSession.cs:253-324 |
| H-DOM-18 | #2572 | StageQuestionSet, SQSVersion, StageQuestionSetVersionReference, Stage.AnnotationQuestions projection |
Per-stage question subsets and stage-version pins on evidence | Avoid | SP-R01 (stage filter defines the pool), RD-R04, RD-R31 (stage binds form identity); the projection silently returns the latest SQS for migrated projects | SP §3.3; BC-R15 (legacy stage sets are conversion input only); T-SP-03, T-BC-03 | Not ported | — | — | QV/StageQuestionSet.cs:38-150 @af51366; Core/Model/ProjectAggregate/StageEntity/Stage.cs:128-147 |
| H-DOM-19 | #2572 | ThrowIfMigratedForLegacyMutation/Projection on Project and Stage |
Refuses legacy question writes and projections once a project is in the new mode | Adapt | C16 E49: legacy aggregate methods check CanonicalScopes |
C16 (R0 floor); integrated-plan §5 R0 item 3; T-BC-06 (no R0 row exists) | Key on the CanonicalScopes marker per scope (Project and Study) with IAggregateWriteGuard, not on MigrationStatus |
LegacyQuestionManagementBoundaryTests (6) as the pattern |
S | Core/Model/ProjectAggregate/Project.cs:643-660 @af51366; Stage.cs:318-326 |
| H-DOM-20 | #2572 | MigrationStatus, Mark/ApplyMigratedQuestionManagement, Mark/ApplyRolledBackQuestionManagement |
Opt-in per-project mode with rollback that discards the PQS and SQS history and allows re-migration | Avoid | AC-M0-04 (no destructive rollback or hand-back); BC-R01, BC-R27, BC-R28, BC-R29 | BC §4.9 | Not ported | — | — | Project.cs:221-285 @af51366 |
| H-DOM-21 | #2572 | AnnotationQuestion.CreateExportProjections, CreateOrdered*ExportProjections |
Projects v2 questions back into legacy AnnotationQuestion from LatestVersion for exports |
Avoid | C11 and VM §10.2 (exports resolve pinned versions and carry option IDs); §12.6 (no stage-keyed export selectors); drops conditions (TargetDto(parent, null)) |
C11; T-RI rows | Not ported | — | — | Core/Model/ProjectAggregate/AnnotationQuestion.cs:193-283 @af51366 |
| H-DOM-22 | #2572 | Extracted OutcomeData, OutcomeDataMutationMapper |
Versioned outcome rows referencing experiment, cohort and outcome annotations | Reference only | C14 (observations on instance IDs); BC-R19 (untouched defaults become ValueOrDefaultUnknown; A copies values verbatim) |
C14; BC §3.4; T-BC-05 | Field inventory only | OutcomeDataMutationMapperTests (4) as cases |
— | Core/Model/OutcomeDataAggregate/OutcomeData.cs:15-45,382-417 @af51366 |
| H-DOM-23 | #2572 | VersionAudit, typed VersionReference records, VersionChangeReasons |
Provenance record and typed cross-aggregate version references | Adapt | C1 and C3 provenance (real actor, effective author, operation, command ID, HLC, digest); VM §12.3–12.4 (I1, I2) | F1a value objects; C1, C3; T-RD-01 | Extend the audit; typed references back the integrity checker; rename QuestionRef (a pin in A, an identity in the package) |
— | S | QV/VersioningValueObjects.cs:11-95 @af51366; QV/VersionChangeReasons.cs:9-21 |
| H-DOM-24 | #2572 | ReplacementDraftLineagePlanner, DraftPQS.ReplacePublishedSubtree |
Clones a published subtree into new drafts with rewired parents and an old-to-new ID map (injected ID factory) | Reference only | Not the versioning mechanism (RD-R30, D2-03; §12.6); the algorithm fits catalogue copy (VM §4.5, ACD §3.4: copies are new identities with copiedFrom) |
ACD §3.4; T-AC-04 | Algorithm and deterministic tests as input to catalogue copy | ReplacementDraftLineagePlannerTests (6) as cases |
— | Core/Services/ReplacementDraftLineagePlanner.cs:10-70 @af51366; QV/ProjectQuestionSet.cs:68-105 |
| H-DOM-25 | #2461 | Domain, service, API and web code of the umbrella | The same as B, C or D, or older than A's tip | Avoid | Superseded by #2572's tip and #2573–#2575; blob comparison in §1.2 | — | Nothing to port | — | — | Blob comparison: 419 of 437 changed paths byte-identical to a stack tip or main; e.g. QV/AnnotationQuestionV2.cs blob 0253500 equals 36190433d, B and C @1ca9f5d |
| H-DOM-26 | #2461 | docs/features/question-management/planning-archive/*, older annotation-impact.service.spec.ts, older session-lifecycle.spec.ts |
Planning archive and stale copies | Avoid | Superseded by main's docs/planning/qm-v2-context/ (D001–D012, K001–K019), D's newer spec and main's renamed e2e spec |
— | Nothing to port | — | — | planning-archive/decisions.md @1ca9f5d; main docs/planning/qm-v2-context/README.md @7673ed0 |
Counts (26 entries): Reuse 0; Adapt 9 (H-DOM-01, 02, 06, 07, 08, 13, 14, 19, 23); Reference only 8 (04, 05, 10, 11, 16, 17, 22, 24); Avoid 9 (03, 09, 12, 15, 18, 20, 21, 25, 26). Nothing ports as is: every adapted piece needs option IDs, pinned versions, form scope or fail-closed mapping first.
3. Defects not to carry over¶
Known facts, checked.
| Fact | Result | Evidence |
|---|---|---|
| B, C and D sit on A's squash; A has "about 51" later commits | Confirmed, corrected to 44 (37 non-merge, 7 merges) | git rev-list 36190433d..af5136696; merge-base --is-ancestor 4b5c6a8d32 fails for B, C and D |
| #2635 duplicate-annotation-ID guard | Confirmed: A's loop overwrites in-payload duplicates silently; main rejects them | A AnnotationRelationshipValidator.cs:56-60; main :41-63 (8c67a0b86) |
| #2648 Study under custom Study parent | Confirmed: A emits AQ009 for any parented Study question | A CandidateProjectQuestionSetValidator.cs:231-238; main AnnotationQuestionPlacementValidator.cs:54-87 |
| #2651 anchor split | Confirmed: A treats any system parent as first-level | A CandidateProjectQuestionSetValidator.cs:242; main AnnotationQuestionPlacementRules.cs:156, AnnotationQuestionPlacementValidator.cs:80 |
C's system-question _id collision |
Root cause is in A: the factory passes the fixed GUID as the aggregate ID per project | SystemQuestionFactory.cs:28-29; AnnotationQuestionV2.cs:288-312 |
| B's transition never rebases type or shape | Consistent: A's primitive only copies, remaps strings or clears, and EnsureCompatible throws on a changed shape |
AnnotationAnswer.cs:27-57; Annotation.cs:351 |
| PR-A's ADR-010 collides with main | Confirmed and extended: ADR-011 and ADR-012 also collide | main docs/decisions/ADR-010..012-* |
New defects found at A's tip.
| ID | Defect | Evidence @af51366 |
|---|---|---|
| N1 | Conditional-parent applicability is lost. The v2 question model has no field for Target.ConditionalParentAnswers; CreateMigratedFromEmbedded sets answerOptionFilters: null; C's converter never reads conditions (and "repairs" missing parents, against BC-R26's no-lossy-conversion rule) |
QV/AQVersion.cs:10-68 (no condition field; AnswerOptionFilters untyped at :56); QV/AnnotationQuestionV2.cs:255-286; #2574 Core/Services/ProjectQuestionMigrationDomainService.cs:132-165 @1b93cbb |
| N2 | A publish-triggered session version copies the latest Status, so it can stay Completed after re-answer cleared answers (against RD-R23) |
AnnotationSession.cs:264-277 |
| N3 | ReAnswer clears the answer in the new version rather than keeping the pin and flagging it |
AnnotationAnswer.cs:54 |
| N4 | Mapping turns a null string answer into "" |
AnnotationAnswer.cs:78-83 |
| N5 | No-op detection compares record payloads holding List<T> by reference, so array answers always append a spurious version |
Annotation.cs:288-295; AnnotationAnswer.cs:138-174 |
| N6 | Unknown legacy annotation types become strings, and unknown data types become String, instead of failing closed |
AnnotationAnswer.cs:101; Annotation.cs:547 |
| N7 | System seed descriptions were edited ("double-space fix", 34171c104), so seq 1 would not equal production definitions |
SystemQuestionFactory.cs:119-127 vs main AnnotationQuestion.cs:724,739 |
| N8 | Rollback discards all PQS and SQS history; MarkQuestionManagementMigrated accepts RolledBack → Migrated |
Project.cs:221-283 |
| N9 | FromExtracted passes no child IDs, so the child-resolution check is skipped for extracted answers |
AnnotationValidationState.cs:32-42 |
| N10 | Stage.AnnotationQuestions silently switches to the latest SQS for migrated projects, so legacy readers see a set no session pinned |
Stage.cs:128-142 |
| N11 | Unbounded embedded version arrays in six documents (question, PQS, SQS, annotation, session, outcome) | AnnotationQuestionV2.cs:119; ProjectQuestionSet.cs:149; StageQuestionSet.cs:77; Annotation.cs:44; AnnotationSession.cs:38; OutcomeData.cs:38 |
4. Superseded by main since the PR was written¶
- Validator hardening: #2635 (
b509595e6,8c67a0b86, 11 May) owns the overlap and duplicate rules A rewrote. - Placement rules: #2648 (merged 19 May) and #2651 (
1c39bbab0, 19 May) moved toAnnotationQuestionPlacementRules/AnnotationQuestionPlacementValidator; A's validator copies the older rules. - Stage capacity fields:
EnforceAnnotationTarget,IdleSessionTimeoutMinutes,StageUpdateDtofields andAnnotationStageSettingsare on main (Stage.cs:18,53-81,335-346,464 @7673ed0). A'sStage.csandStageUpdateDto.csedits duplicate them, and D2-07 moves them to the form. - Docs: #2398 (
14f2dd375) deleted the three docs A edits and addeddocs/planning/qm-v2-context/, which preserves #2461's planning archive. - ADR numbers 010–012 were used on main for unrelated decisions.
- Active-reviewer e2e: main's
e2e/tests/session-lifecycle.spec.ts(79cf01ab5,8f9659847) supersedes #2461's copy.
5. Closure-note drafts (for use after the hold lifts and G0-D4 is decided; not authorised now)¶
#2572. Harvested under Q-08 into the integrated review plan. Ideas ported in adapted form: the typed answer payload and its shape check, the legacy question-shape mapping, the system-question definitions as the global seed, the form-version pin guards, the composition rules and AQ rule codes, the legacy-write guard pattern, and the provenance and version-reference value objects. They now sit under T-RD-01, T-RD-02, T-RD-04, T-BC-01 and T-BC-06. Not carried: the extracted Annotation and AnnotationSession aggregates (embedded version arrays, stage-keyed), per-stage question sets, single mutable drafts, publish transitions that clear answers or copy status, and rollback to the embedded model. ADRs 010 to 012 stay as reference only, because their numbers are taken on main.
#2461. This umbrella was split into #2572–#2575. Every changed file is byte-identical to a file in
that stack or on main, or is an older copy. Its planning context is preserved on main in
docs/planning/qm-v2-context/. Nothing further is harvested. Close it with the stack.
6. Proposed tracker rows¶
Attach to existing rows where possible:
| Row | Attach | Acceptance evidence to add |
|---|---|---|
| T-RD-01 (F1a) | H-DOM-02 payload value object, H-DOM-06 shape mapping, H-DOM-07 D2-06 seed contract, H-DOM-23 provenance and reference value objects; H-DOM-10 and H-DOM-11 as ADR inputs | RD-AE29 (D2-03, D2-06 fixtures) plus a seed-parity fixture: seq 1 equals main's system definitions for every SystemQuestionVersion |
| T-RD-02 (R2a) | H-DOM-08 form-version pin guards, H-DOM-13 composition validation with option IDs | C4-T conformance; the #2648 and #2651 cases pass through the new composition validator |
| T-RD-04 (F2) | H-DOM-01 for policy generations; H-DOM-03, H-DOM-04 and H-DOM-17 as reference | RD-AE16 includes N2 and N3 as negative fixtures |
| T-BC-01 | H-DOM-06, H-DOM-14, H-DOM-07 parity | The dry run reports N1-type loss: every legacy conditional parent maps to an option-ID condition, or is a manifest exception |
| T-BC-06 | H-DOM-19 guard pattern (R0 has no tracker row) | LegacyQuestionManagementBoundaryTests pattern rekeyed on CanonicalScopes |
| T-AC-04 | H-DOM-24 as algorithm input | — |
New row (none fits AC-M0-04):
| ID | Scope | Owner | Depends on | Decisions | Spec sections | Acceptance evidence | Release/gate | Brief | Implementation authorisation | PR/status | Evidence | Blockers |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| T-RD-10 | M0: replace versioning-model §12.6 with the full QM v2 harvest-and-avoid map for #2461 and #2572–#2575 (this audit plus B, C and D); closure notes ready for G0-D4 | Stream A lead | T-G0 (G0-D4), T-HOLD for any closure | Q-08 | VM §12.6; RD §14 | AC-M0-04; every H-DOM entry with a target row or "Avoid" reason | M0 | Drafted (this audit) | On hold | — | This file | Hold; G0-D4 not authorised |
7. Owner-level questions¶
None. Every verdict follows from a decided rule or a recorded PROPOSAL. D2-03 (data type and
multiplicity as version content) is the only open engineering contract that would revive
H-DOM-24 as a versioning mechanism. It stays a brief item under T-RD-01.