Harvest map: existing question-management work and dormant PRs¶
Status on 5 October 2026. This is a temporary planning document. Feature implementation is on hold (Chris, 5 October 2026) and G0 is not approved. Nothing has been ported, rebased, merged or closed. This map is planning input for the release briefs: no harvest and no PR closure is authorised by it, and every open PR it covers stays open and untouched.
1. Summary in plain English¶
What exists. Thirteen open PRs hold earlier work on question management and its neighbours:
- the dormant Question Management v2 (QM v2) stack: #2572 (domain), #2573 (services), #2574 (migration and API) and #2575 (web), with the #2461 umbrella they were split from;
-
2224, custom project groups, by
nurikarakaya, who has left;¶ -
3934 and #2781, question template import (#3934 is Chris's recent work, frozen by the hold);¶
-
2387, child-question checks and an assign tree for the current new editor;¶
-
2986, #2987, #2629 and #2812, answer validation and the schema, profile and response-mode¶
designs.
Overall verdict. The plan does not start from scratch, but almost nothing ports as is. The QM v2 stack was built for a model the owner session has replaced. Its ideas, test cases and several algorithms are worth keeping. Its storage shapes, drafts, stage-keyed publishing and migration are not. #3934 is the exception: most of it carries into R1a nearly unchanged.
What M0 and the later releases reuse.
- M0 writes the harvest-and-avoid record into its report (AC-M0-04), so each adapted piece
lands in the brief of the release that will build it (
T-RD-10). - F1a takes the typed answer payload, the legacy question-shape mapping, the system-question definitions as a global seed, the provenance value objects, #2986's shared fixtures and error codes, and #2812's response-mode contract.
- R1a takes #3934's import reader, plan, controller and transactional receipt, #2781's golden fixture and #2387's child-validity rule.
- R1b and R1c take #2224's API shape, domain rules and tests, rebuilt on membership schema 1.
- R2a, F2 and R2c take the composition validators re-keyed to option IDs, the form-version pin guards, the save-conflict contract, the publication planner's shape, the impact counts and digest, and the reviewer's post-publication alert.
- Conversion takes PR-C's plan builder, review-state extraction, parity checker and legacy-shaped projection, all retargeted to the new records.
What is avoided, and why.
- PR-C's migration erases the embedded legacy data from pmStudy, and its rollback is a lossy hand-back to the legacy model (H-MIG-05, H-MIG-08). This directly contradicts BC-R27, BC-R29, C16 and AC-M0-04. Neither piece is carried.
- Per-stage question sets, stage transitions and the review and export locks: under the stage study filter a stage owns no evidence, target or session.
- Single mutable drafts: the owner asked for collaborative drafts with an audit trail.
- Publish transitions that clear answers or copy status, and mapping by value: RD-R21 and RD-R23 forbid both.
- Version arrays embedded in documents, per-project copies of system questions, #2224's schema-0 groups and its default that lets administrators assign permissions, and #2781's production uploader.
- Work that
mainhas since done another way: the AF2 scaffold, active-reviewer presence, the ADR-009 extraction and the #2467 copies inside PR-C.
Counts. The five audits hold 131 entries: 6 Reuse, 41 Adapt, 35 Reference only and 49 Avoid. Five of them (three Reuse, two Reference only) sit outside this programme. The ported effort is roughly 50 to 123 working days, about 95 days at a central estimate (§9).
When. Harvesting is planned work. It waits for G0 and the hold lift, and each port happens in its release's slice under that release's approved brief. The PRs stay open until their harvest is done. Closing any of them is a later decision for Chris (G0-D4 to G0-D7, §8.2).
2. Authority and boundaries¶
- Q-08 (Chris, 3 October 2026). Harvest the dormant QM v2 stack (#2572 to #2575, with the #2461 umbrella) rather than revive it: audit it against the new contracts, port what fits into small new PRs and close the stack once harvested. AC-M0-04 makes the harvest-and-avoid table part of M0.
- Q-09 (Chris, 3 October 2026). Harvest #2224's API shape and tests into R1c, then close it with a note crediting its author. AC-R1c-12 records both.
- The hold. Feature implementation is on hold (Chris, 5 October 2026). The PR dispositions in the G0 dossier are not authorised: G0-D4 (QM v2), G0-D5 (#2224), G0-D6 (#2986) and G0-D7 (#2987, #2629, #2812). No G0 item yet covers closing #3934, #2781 or #2387 (§10). The rollout plan §11 lists PR closure among the actions that never happen without separate approval.
- This map is planning input. Each port is a slice of the release that owns it. It is built
only under that release's approved brief, after its freeze gate, with G0 approved and the hold
lifted (D1-04). Every port is a fresh PR from
main; no stack branch is rebased, merged or cherry-picked. A verdict here is a recommendation that a brief may overturn with a recorded reason. - Relation to the versioning model. Versioning model §12.6 was the only harvest table before this map. It covers versioning alone and predates the owner session. Where the two differ, this map applies; §7.2 notes each row.
3. Sources¶
Five read-only audits compared these heads with the nine specifications on 5 October 2026, against
main at 7673ed0d3. Nothing was checked out, fetched, built, committed or commented. They are
kept verbatim in reviews/harvest-2026-10-05/:
audit A (#2572, #2461),
audit B (#2573, #2986,
2987, #2629, #2812), audit C¶
(#2574), audit D (#2575,
2387) and audit E¶
(#2224, #3934, #2781). The table's states and sizes were read with gh pr view on the same day.
Titles are as on GitHub, with dashes normalised to colons.
| PR | Title | Head | Base | State | Size | Last updated |
|---|---|---|---|---|---|---|
| #2461 | feat(qm): Question Management v2: R1 domain + API + web scaffolds (WIP) | 1ca9f5def |
main |
Open, draft, conflicting; do-not-merge umbrella | 738 files (+70,509/−9,289) against today's main; 437 changed files against its own merge base |
22 Sep 2026 on GitHub; commits 20 Mar to 24 Apr |
| #2572 | feat(qm): Question Management v2: Domain foundation (PR-A of 5) | af5136696 |
main |
Open, conflicting | 67 files (+10,283/−184), none generated | 1 Sep on GitHub; last commit 26 Apr |
| #2573 | feat(qm): Admin decision framework domain services (PR-B of 5) | 271290115 |
feat/qm-v2-a-domain |
Open, clean against its stacked base | 8 files (+1,694/−13) | 17 Apr |
| #2574 | feat(qm): Migration, ADR-009 cutover, and API (PR-C of 5) | 1b93cbbc4 |
feat/qm-v2-b-admin-services |
Open, mergeable (unstable) against its stacked base | 604 files (+34,231/−3,992); own QM work 138 files plus edits in 24 mixed files (+16,966) | 24 Apr |
| #2575 | feat(qm): Web scaffolds + docs (PR-D of 5) | 098330759 |
feat/qm-v2-c-migration-cutover |
Open, mergeable (unstable) against its stacked base | 544 files (+44,007/−7,385); own work 158 non-generated files (about +30.5k) | 24 Apr |
| #2224 | implement custom project groups feature (author nurikarakaya) |
fff8385ac |
main |
Open, conflicting | 47 files (+4,850/−543); about 1,900 lines of real logic | 22 Sep on GitHub; last main merge 5 May |
| #3934 | feat(annotation): implement question template import through the UI | 9d6c596cf |
main |
Open, conflicting in generated files only | 42 files (+6,192/−20); about 1,800 lines of real logic | 2 Oct |
| #2781 | feat(importer): add guarded annotation question import foundation | 2d8b071b0 |
main |
Open, conflicting | 19 files (+10,044/−5), Python tooling | 1 Sep |
| #2387 | feat: QM child question visualization and assign tree improvements | c81426c44 |
main |
Open, conflicting; CI red on the head | 46 files (+3,819/−1,127) | 1 Sep on GitHub; feature commit 10 Mar |
| #2986 | fix(annotation): validate current-schema answers before persistence | 1c6799b00 |
main |
Open, mergeable, blocked by review rules | 8 files (+1,306) | 1 Sep |
| #2987 | docs(annotation): define schema and profile architecture boundaries | efd4b96ba |
main |
Open, mergeable, blocked | 1 file (+258) | 1 Sep |
| #2629 | docs(annotation): scope FEAT-027 annotation validation architecture | d761ca442 |
main |
Open, mergeable, blocked | 1 file (+320) | 1 Sep |
| #2812 | feat(annotation): define response-mode gate contract | 1c3869224 |
main |
Open, conflicting | 39 files (+1,112/−162), 12 of them generated | 1 Sep on GitHub; last commits 27 Aug |
Path abbreviations in the evidence column. PMC/ = src/libs/project-management/SyRF.ProjectManagement.Core/
(audit A's Core/); QV/ = PMC/Model/QuestionVersioning/; PMA/, PMD/ and PMT/ = the
Application, Mongo.Data and Core.Tests projects beside it; API/ = src/services/api/SyRF.API.Endpoint/;
QMV2/ = src/services/web/src/app/project/project-admin/question-management-v2/; QMS/ =
src/services/web/src/app/core/state/question-management-v2/; AF2/ =
src/services/web/src/app/shared/annotation/annotation-form-v2/; QM/ =
src/services/web/src/app/project/project-admin/question-management/. Short SHAs follow the audits;
@7673ed0 is main.
4. Per-PR verdicts¶
4.1 #2461, the QM v2 umbrella¶
- Holds. The original combined QM v2 R1: domain, API and web scaffolds, 309 commits from 20
March to 24 April 2026. Against its merge base
de8e312adit changes 437 files plus 2 deletions (+77,083/−5,462). - State and drift. A draft marked do-not-merge, conflicting with
main. A blob-by-blob comparison found 419 of the 437 paths byte-identical to a stack tip ormain. The other 18 are infrastructure drift, five planning-archive files and three older copies. Its domain code is at PR-A's squash level, without any of PR-A's later fixes. Its planning decisions (D001 to D012) and knowledge (K001 to K019) are onmainindocs/planning/qm-v2-context/. - Disposition. Reference then close, with the stack. Nothing is harvested (H-DOM-25, H-DOM-26).
- Closure-note draft (not to be posted while the hold lasts; posting it needs G0-D4):
This umbrella was split into #2572 to #2575. Every changed file is byte-identical to a file in that stack or on
main, or is an older copy (harvest map H-DOM-25 and H-DOM-26). Its planning context is preserved onmainindocs/planning/qm-v2-context/. Nothing further is harvested, and it closes with the stack.
4.2 #2572, QM v2 PR-A (domain foundation)¶
- Holds. 67 files and no generated code, API, web or migration. The question-versioning types, extracted Annotation, AnnotationSession and OutcomeData aggregates, services and validators, 169 tests, and ADR-010 to ADR-012 drafts. Nothing is wired to an endpoint.
- State and drift. Conflicting with
main: 14 conflict hunks in 6 files and 3 modify-or-delete conflicts, from a merge base of 25 April. After its squash36190433d, PR-A's tip has 37 of its own non-merge commits plus 7 merges ofmain; B, C and D never absorbed them (§8.3). ADR numbers 010 to 012 are all taken onmain. Git flags none of three semantic conflicts withmain: #2635's duplicate-annotation-ID guard, #2648's nested Study parents and #2651's anchor split. - Disposition. Harvest then close. 9 Adapt, 8 Reference only, 7 Avoid; nothing ports as is. (Audit A's other two Avoid entries, H-DOM-25 and H-DOM-26, are #2461's.)
- Closure-note draft (not to be posted while the hold lasts; posting it needs G0-D4):
Harvested under Q-08 into the October 2026 integrated review plan (harvest map H-DOM-01 to H-DOM-24). Ported in adapted form: the typed answer payload and its shape check, the legacy question-shape mapping, the system-question definitions as a global seed, the form-version pin guards, the composition rules and rule codes, the legacy-write guard pattern, and the provenance and version-reference value objects (tracker rows
T-RD-01,T-RD-02,T-RD-04,T-BC-01andT-BC-06). Not carried: the extracted aggregates with embedded version arrays, per-stage question sets, single mutable drafts, publish transitions that clear answers or copy status, and rollback to the embedded model. ADR-010 to ADR-012 stay reference only; their numbers are taken onmain.
4.3 #2573, QM v2 PR-B (admin decision services)¶
- Holds. One commit on PR-A's squash, 8 files: an impact service with a fingerprint, a session transition planner, a leased stage-transition job, typed exceptions and 13 tests. No tests cover the job.
- State and drift. Clean against its stacked base. Unlike C and D, it never merged
main. None of its classes exist onmain, and it inherits PR-A's conflict. - Disposition. Harvest then close. 4 Adapt, 4 Reference only, 5 Avoid. Its core idea, one attributable session version per affected session per publication, returned with the owner's D2-01 reversal; its stage-keyed job and locks did not.
- Closure-note draft (not to be posted while the hold lasts; posting it needs G0-D4):
Harvested into the integrated review plan (harvest map H-SVC-01 to H-SVC-13). The core idea, that a publication writes one attributable session version per affected session combining every question's treatment, returned with the owner session's D2-01 reversal. It is specified in RD §3.15 and in C4 and C5 as
PublicationGenerated, and the impact counts and fingerprint inform the R2c preview and its digest (T-RD-04,T-RD-05). The stage-keyed job, the review and export locks, value-based mapping, answer clearing and replacement across questions are not carried: stages own no evidence, and publication uses the shared operation family.
4.4 #2574, QM v2 PR-C (migration, cutover and API)¶
- Holds. 604 files on GitHub, but C's own question-management work is 138 files plus edits in 24 mixed files (+16,966 lines: 8,813 source, 8,153 tests). It covers migration, cutover readers and statistics, migrated review writes, a 17-endpoint API and export modes. It also embeds 73 files derived from #2467 (not 75) and 22 of #2543's files.
- State and drift. Mergeable only against its stacked base. Against
maina trial merge gives 545 conflict blocks in 61 files plus 29 files added on both sides, and 103 of its 225 own files have changed onmain. #2467 merged on 30 August and #2543 on 27 April, by other routes. - Headline. Its migration clears the embedded annotations, sessions and outcome data from each Study and saves the Study by full replace, so the legacy originals in pmStudy are erased (C-D05). Its rollback rebuilds legacy records from the extracted ones, losing fields and flattening any work saved after migration into the legacy model (C-D07). Both contradict BC-R27, BC-R29, C16 and AC-M0-04.
- Disposition. Harvest then close. 8 Adapt, 3 Reference only, 11 Avoid.
- Closure-note draft (not to be posted while the hold lasts; posting it needs G0-D4):
Harvested into the October 2026 plan (harvest map H-MIG-01 to H-MIG-09 and H-API-01 to H-API-13). The conversion planner, review-state extraction, parity checker, canonical-to-legacy projection and export mode reservation carry forward as adapted designs and test cases, into the baseline-conversion rows
T-BC-01,T-BC-03,T-BC-05andT-BC-06, the R2a, F2 and R2c rowsT-RD-02,T-RD-04andT-RD-05, and the export rowsT-RI-08andT-RI-12. The erasure of embedded state, the reconstructive rollback, per-project system-question copies, the stage-publish transaction and stage transitions are deliberately not carried. The embedded #2467 and #2543 files are superseded by those PRs' own merges intomain.
4.5 #2575, QM v2 PR-D (web scaffolds and docs)¶
- Holds. 544 files on GitHub; D's own commit has 158 non-generated files (about +30.5k lines), of which about 6.7k are designer code and tests. The rest is an early AF2 scaffold, presence web code, docs and a prototype. Nothing was wired to production.
- State and drift. Mergeable only against its stacked base. Against
mainit conflicts in substance: mainhas its own AF2 at the same path, lifted from this PR;- presence is already on
main; - its ADR numbers collide;
- it reuses the
newQuestionManagementflag and path for a different editor; - it targets Angular 21, where
mainruns 22.1.
Correction: main is not zoneless by default. Change detection is chosen at bootstrap, the
zonelessChangeDetection flag defaults to false and no environment sets it. Guard specs still
enforce zoneless-safe code, so any ported component must pass them.
- Disposition. Harvest then close. 2 Adapt, 9 Reference only, 9 Avoid.
- Closure-note draft (not to be posted while the hold lasts; posting it needs G0-D4):
Harvested into the October 2026 plan, not merged: the reviewer's post-publication alert, as input to the R2c in-form alerts (
T-RD-05); the publish base-version and impact-fingerprint check, as the R2c preview-digest recheck; and the UI specification, publishing UX and prototype, as design references for designer history with a diff (AC-R2c-27) and the keyboard model (AC-R1a-12). The AF2 scaffold already lives onmain(74dffd658), and the active-reviewer web code merged by another route. The per-stage Assign and publish model, the root store and the properties panel are superseded by the owner-session specifications, and so are the April release docs. The defects found are recorded in the harvest map so they are not reintroduced: the wizard's injector error, the no-op undo, the autosave that wipes options and the discarded admin decisions.
4.6 #2986, current-schema answer validation (G0-D6)¶
- Holds. 8 files: a conformance validator (419 lines), a ProblemDetails model, a malformed lookup-ID rule, 15 tests and one 11-case fixture run by .NET, AF1 and AF2.
- State and drift. Mergeable, blocked by review rules; none of its files changed on
main. It validates no write today: nothing outside its tests calls the validator (V-D1), and its own checklist leaves the integration unticked. The blocker it waited for, #2467, merged on 30 August. - Disposition. Depends on G0-D6. The dossier's premise, "it fixes today's legacy writes", is false until it is wired. Recommended: confirm the deviation with a corrected rationale (§10, item 1), keep it open outside this programme, and harvest its fixtures and rule codes into E23 and the conversion inventory either way (H-VAL-02 to H-VAL-04).
- Closure-note drafts (not to be posted while the hold lasts):
If G0-D6 is confirmed: stays open outside the programme as the legacy-write integrity fix, to be finished by wiring both submission paths. Its fixture corpus and rule codes are harvested into E23 and the
T-BC-01inventory (harvest map H-VAL-02 to H-VAL-04).If G0-D6 is declined: closed after harvesting the same items into
T-RD-01,T-RD-02andT-BC-01. Legacy writes stay unvalidated until R7.
4.7 #2987, #2629 and #2812, the schema, profile and response-mode designs (G0-D7)¶
- Holds. #2987 is one ADR (258 lines) separating schema from profile, with the five semantic categories of a response. #2629 is one draft feature doc (320 lines) scoping FEAT-027. #2812 has 27 authored files: ADR-017, an extensibility-doc rewrite and inert flag plumbing, plus 12 generated files.
- State and drift. #2987 and #2629 are mergeable but blocked; #2812 is conflicting, with 28 of
its 39 paths changed on
main. ADR-016 and ADR-017 are taken onmain, and #2629's planned "ADR-011 Project Template" collides withmain's ADR-011. #2987 retires #2629's runtime design. - Disposition. Harvest into the F1a C4 draft (and the F-O C14 ADR for #2812's units and metadata types), then close each.
- Closure-note drafts (not to be posted while the hold lasts; posting them needs G0-D7):
#2987. Harvested into the F1a C4 draft: server authority, the five semantic categories, the ban on
LatestVersionand the lifecycle (harvest map H-VAL-07, H-VAL-08). The schema and profile split and the per-stage binding are superseded by question and form versions. ADR-016 is taken onmain; the content lands in a programme ADR numbered from 030 to 069.#2629. Superseded by #2987 and #2986 and by E23's shared-fixture applicability specification. Its problem statements are kept as H-VAL-10.
#2812. The response-mode wire contract is harvested into C4 and E37, with metadata types and units into C14 (H-VAL-12, H-VAL-13). The schema-v0 activation path, the per-stage lock and the three-service flag are not carried: response modes are canonical-only, and R7 retires legacy writers. The fail-closed flag parsing is proposed as a separate small fix (H-VAL-15). ADR-017 is taken on
main.
4.8 #2224, custom project groups (G0-D5)¶
- Holds. Work by
nurikarakayafrom December 2025 to January 2026: about 300 backend and 1,600 frontend lines of real logic. It has group create and delete endpoints, domain rules with a cascading delete, schema-0 persistence, a default that lets Administrators assign permissions, a group dialog, members-page components and about 1,400 lines of .NET tests. - State and drift. Conflicting; 25 of its 47 files changed on both sides since 4 May. Bots
reviewed it; no person did. Nothing on
maincreates a custom group yet. - Disposition. Harvest into R1c (
T-AC-10), with two inputs to the R1b page (T-AC-11), then close (T-AC-12). The schema-0 persistence and the permission default are avoided. - Closure-note draft (not to be posted while the hold lasts; posting it needs G0-D5):
Thank you, @nurikarakaya, for the custom project groups work (December 2025 to January 2026). Its API shape (
POSTandDELETE api/projects/{projectId}/groupsunder EditMemberships), its create-and-cascade-delete rules and its domain, controller and permission-isolation tests are carried into R1c of the integrated review plan, delivered with the authorization programme's WP11 once memberships are on schema 1 (harvest map H-GRP-01, H-GRP-02 and H-GRP-09 to H-GRP-11). The schema-0 persistence path and theAssignPermissionsdefault are not carried, because memberships migrate to schema 1 first andAssignPermissionsstays owner-reserved. The members-page components inform the new Members & groups design.
4.9 #3934 and #2781, question template import¶
- Holds. #3934 has a bounded CSV and XLSX reader, a canonical plan with a legacy adapter, a transactional apply with a receipt and attempt history, a flagged controller, an Angular dialog and 45 API tests. #2781 is Python tooling with a reference parser, a synthetic golden fixture and a production uploader that fails closed.
- State and drift. #3934 is recent work that Chris started on 2 October; the hold froze it, so
it is not dormant. Its conflicts are generated files only, and its one red .NET test is in a
project it does not touch. #2781 is 686 commits behind
main. The uploader that ran in August lives outside the repository. - Disposition. #3934: harvest by re-cutting it on
mainand splitting it into R1a-3 and R1a-4 (T-RD-11), then close it as superseded by those PRs. #2781: harvest its fixture and test cases, then close. Both closures areT-RD-16, and no G0 item covers them yet (§10). - Closure-note drafts (not to be posted while the hold lasts):
#3934. Harvested into R1a. The bounded reader, the canonical plan and legacy adapter, the controller and the transactional receipt with its tests are split into R1a-3 (preview behind the import-target port) and R1a-4 (legacy apply), with the dialog adapted to the new editor's design (harvest map H-IMP-01 to H-IMP-07). The canonical adapter, which writes versioned question drafts with import or
copiedFromprovenance, follows in R2a.#2781. The reference parser, shared fixture and Python tests shaped #3934's legacy adapter. The fixture becomes a .NET golden test in R1a-3, and the test cases are used to find coverage gaps (H-IMP-08, H-IMP-09). The live uploader, its production impersonation path and its CI lane are not carried, because the production path is the server-side atomic import.
4.10 #2387, QM child visualisation and assign tree¶
- Holds. 46 files: 2024 work by Mala K and Chris, a feature commit of 10 March 2026 and a merge
of
main. Much of the diff is formatting churn, and two stray files sit at the repository root. - State and drift. Conflicting; 27 of its 46 paths changed on
mainsince March. The head does not compile (D-D13), and CI is red. - Disposition. Harvest then close. 2 Adapt, 1 Reference only, 5 Avoid. Closure is
T-RD-16. - Closure-note draft (not to be posted while the hold lasts):
Harvested: the child-question validity rule (a child condition naming a parent option that no longer exists), as the R1a editor warning and the R2a composition refusal (AC-R2a-24, FX-VM-10), re-keyed on option IDs; and the
computedPrevioustests, formain's signal utilities. The sticky flat Assign tree, the template-driven validation framework, theMatOptionsubclass and the navigation changes are superseded bymainor retired, because forms replace per-stage question selection. Thanks to Mala K and Chris for the 2024 child-visualisation work.
5. Harvest entries¶
All 131 entries, merged from the five audits with their IDs kept. They are grouped by the release
that first uses them. The Target column names a second release or row where an entry splits.
Effort is the audits' grading for the port or adaptation including tests: S one day or less,
M one to three days, L more than three days, — nothing to port. Defect IDs such as
C-D05 are explained in §6.2.
5.1 M0: the harvest record (T-RD-10)¶
| ID | PR | Component | Verdict | Target (spec §, contract, release, tracker row) | Adaptation | Effort | Evidence |
|---|---|---|---|---|---|---|---|
| H-WEB-17 | #2575 | D-RS-01 to D-RS-05 in release-strategy.md |
Reference only | AC-M0-04 record; RD §4.8; BC §10; C16; T-RD-10 |
Record each as superseded: D-RS-01 is the F1a and G0 model; D-RS-02 becomes Study.CanonicalSummary and the C16 readers; D-RS-03 becomes versions with "removed question" treatments (AC-R2c-18); D-RS-04 becomes universal conversion with routing rollback; D-RS-05's batching survives as phase 2 per Study, without freezing review. D-RS-03's dialog wording is copy input for AC-R2c-18 |
— | docs/features/question-management/release-strategy.md:357-408 @0983307 |
| H-WEB-19 | #2575 | Planning and history docs (implementation plan, state of play, reviews, migration strategy, the M011 plan, five April plans, three feature briefs) | Reference only | Decision register §5; T-RD-10 |
Not ported into docs/; cited by SHA here. Their briefs now belong to other lanes: annotation import to XA1, question import to R1a, autosave to AF2 and RD §4.2, training rounds to TI |
— | docs/features/question-management/*.md @71b179c |
5.2 F1a: contract drafts (T-RD-01)¶
| ID | PR | Component | Verdict | Target (spec §, contract, release, tracker row) | Adaptation | Effort | Evidence |
|---|---|---|---|---|---|---|---|
| H-DOM-02 | #2572 | AnnotationAnswer records, EnsureCompatible, FromLegacy |
Adapt | RD §3.9; C1 revision payload, C2; VM §3.4, §6.3; 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 (BC-R08, BC-R26; A-N6); structural equality for list payloads (A-N5) |
M | PMC/Model/AnnotationAggregate/AnnotationAnswer.cs:19-44,89-103 @af51366 |
| H-DOM-05 | #2572 | AnnotationQuestionV2, AQVersion, QuestionOptionV2, AnswerOptionFilter, DraftContent |
Reference only | RD §3.4; C4; VM §3.1 to §3.3; T-RD-01, T-RD-02 |
Keep the identity and content split, init-only identity, never-delete (QD1) and "seed a design draft from an older version". Versions become separate records; requiredness is form-owned (VB-17); entity types replace categories (DD-12); the catalogue's copiedFrom replaces Scope (D2-15). Rename A's QuestionRef, which means a version pin in A and an identity in the package. The model has no condition field (A-N1) |
— | QV/AnnotationQuestionV2.cs:15-135,197-221,335-351 @af51366 |
| H-DOM-06 | #2572 | ChildQuestionScope, AnnotationCollectionMode, ResolveCollectionMode, ResolveValueType |
Adapt | VM §3.1 (repeatable), §11; C2 instance element; D2-03; T-RD-01; mapping in T-BC-01, T-BC-03 |
Fix repeatable at creation, not at the first Multiple publish; unknown DataType fails closed; one mapping table shared by conversion and the designer |
S | PMC/Model/AnnotationAggregate/Annotation.cs:489-549 @af51366; QV/ChildQuestionScope.cs:28-35 |
| H-DOM-07 | #2572 | SystemQuestionFactory (18 definitions with per-version parent and option resolvers) |
Adapt | VM §3.7; RD-R32; D2-06; C4 system row; T-RD-01 (seed contract), T-RD-02; parity input to T-BC-01 |
Global records keyed (systemGuid, systemQuestionVersion, seq) with a structural digest, never per-project documents with the GUID as _id (H-MIG-02); restore main's exact text at seq 1 (A-N7); mint stable option IDs; a parity test against main's AnnotationQuestion.SystemQuestions, including #2651's anchor split |
M | PMC/Services/SystemQuestionFactory.cs:18-50,119-127,171-187 @af51366; PMC/Model/ProjectAggregate/AnnotationQuestion.cs:417-421,724,739 @7673ed0 |
| H-DOM-10 | #2572 | ADR-011 (A): two-level draft and formal versioning | Reference only | F1a storage ADR rationale; RD §3.10; VM §7.7; T-RD-01 |
Cite in the storage ADR; never merge. Its ageing snapshots and per-annotation drafts conflict with RD §3.10, and its number is taken on main |
— | 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 |
Reference only | Domain model §8; F1a naming ADR; T-RD-01 |
An old-to-new name key for reading dormant code; superseded by domain model §8 | — | docs/decisions/ADR-010-qm-v2-ubiquitous-language-renames.md:39-79 @af51366 |
| H-DOM-16 | #2572 | Extracted AnnotationSession, AnnotationSessionVersion and its trigger enum |
Reference only | RD §3.9; C5; VM §7.1 to §7.3; T-RD-02, T-RD-04 |
Keep the full pin-map shape and status derived from the latest explicit version. Sessions are Study × form with deterministic IDs, have drafts (SL1), and the reconciler is not a form session | — | PMC/Model/AnnotationSessionAggregate/AnnotationSession.cs:20-63,326-359 @af51366 |
| H-DOM-23 | #2572 | VersionAudit, typed VersionReference records, VersionChangeReasons |
Adapt | C1, C3 provenance; VM §12.3, §12.4; T-RD-01 |
Add real actor, effective author, operation, command ID, clock stamp and digest; typed references back the integrity checker; rename QuestionRef |
S | QV/VersioningValueObjects.cs:11-95 @af51366; QV/VersionChangeReasons.cs:9-21 |
| H-VAL-03 | #2986 | Shared fixture annotation-answer-conformance-v1.json with .NET, AF1 and AF2 runners |
Adapt | E23; AC-R2a-03 (FX-APPLIC); T-RD-01, T-RD-02 |
Re-key to option IDs and pinned question versions; add applicability, response-mode and metadata cases; keep the three-runner pattern, with AF1 for legacy parity only | S | src/services/web/src/app/shared/annotation/testing/annotation-answer-conformance-v1.json @1c6799b |
| H-VAL-04 | #2986 | Rule codes and AnnotationAnswerConformanceProblemDetails |
Adapt | RD §4.4 ValidationFailed; C18 and E23 error catalogue; T-RD-01, T-RD-02 |
Return every error, not only the first (V-D2); add the C2 context key and the question version; keep the codes stable | S | PMC/Services/Validation/AnnotationAnswerConformanceValidator.cs:10-37; API/Models/AnnotationAnswerConformanceProblemDetails.cs:8-42 @1c6799b |
| H-VAL-07 | #2987 | ADR-016 §1 and §3: server authority, five semantic categories, value XOR mode, suppression derived | Adapt | F1a C4 ADR (a number from ADR-030 to 069, DOM §11.8); VM §3.4; E37; DD-12; T-RD-01 |
Map onto question-version content. Settle in E37 that a response is a value or a responseModeId, plus metadata: #2987 also allows metadata alone (V-D3). A reference response belongs to C1 or C13 entity references, not options |
S | docs/decisions/ADR-016-annotation-schema-profile-boundary.md:57-114 @efd4b96 |
| H-VAL-08 | #2987 | ADR-016 §4 and §5: exact session-to-version chain, LatestVersion prohibited, lifecycle |
Reference only | C4 (QD1), C5; RD §3.9; T-RD-01 |
Cite as provenance; the contracts already cover it | S | Same file, :116-150 @efd4b96 |
| H-VAL-10 | #2629 | FEAT-027 problem statements | Reference only | E23, E24; DD-12; ACD §3.4; T-RD-01, T-AC-04 |
Problem framing only | S | docs/features/annotation-questions/configurable-validation-strategy.md:119-266 @d761ca4 |
| H-VAL-12 | #2812 | ADR-017 response contract (value XOR mode, scoped metadata, stable IDs, no global N/A enum, suppressDescendants by ancestor instance, requiresReason, presence-sensitive updates) |
Adapt | F1a C4 ADR and E37 (T-RD-01); units and metadata types into the F-O C14 ADR (T-RI-11) |
definitionVersion becomes the revision's questionVersionRef; expectedDefinitionVersion becomes the session's declared form version plus StaleBase; drop schema-v0 activation; align units with C14 |
M | docs/decisions/ADR-017-annotation-response-modes-and-metadata-contract.md:28-192,252-273 @1c38692 |
| H-VAL-13 | #2812 | Freeze-on-first-use (Chris, 18 August 2026) | Reference only | C4; VM §3.4 (PH-06) | Provenance for frozen versions. main's extensibility doc still calls the choice "not decided" (§5.14) |
S | Same file, :209-250,341-345,374-417 @1c38692 |
5.3 R0: the compatibility floor (no R0 row; held by T-BC-06)¶
| ID | PR | Component | Verdict | Target (spec §, contract, release, tracker row) | Adaptation | Effort | Evidence |
|---|---|---|---|---|---|---|---|
| H-DOM-19 | #2572 | ThrowIfMigratedForLegacyMutation and ThrowIfMigratedForLegacyProjection on Project and Stage |
Adapt | C16 E49; integrated plan §5 R0 item 3; BC §4.5; T-BC-06 |
Key on the per-scope CanonicalScopes marker (Project and Study) through main's IAggregateWriteGuard, not on MigrationStatus |
S | PMC/Model/ProjectAggregate/Project.cs:643-660 @af51366; Stage.cs:318-326 |
| H-API-06 | #2574 | GuardLegacyReviewPath, Study.UsesExtractedReviewState, ExtractionInfo.ThrowIfUsingExtractedReviewState |
Reference only | R0 floor item 3; C16; BC §4.5; writer list for T-BC-01; T-BC-06 |
Not ported; its guarded methods start R0's writer inventory. It falls short: the flag moves with migration and rollback; screening, consumers, UpdateMany, bulk update and question-delete cascades are unguarded; no CAS; an untyped refusal that loses the draft; no refusal test |
— | PMC/Services/ReviewSubmissionService.cs:14-18,46-51,57-63,72-79 @874a421; PMC/Model/StudyAggregate/ExtractionInfo.cs:398-418 |
5.4 R1a: question templates and import (T-RD-11, T-RD-12, T-AC-04)¶
| ID | PR | Component | Verdict | Target (spec §, contract, release, tracker row) | Adaptation | Effort | Evidence |
|---|---|---|---|---|---|---|---|
| H-IMP-01 | #3934 | QuestionImportFileReader and vendored Unicode case folding |
Reuse | AC-R1a-01 to 04; ADR-009; R1a-3; T-RD-11 |
Move to the Application layer beside the plan; no logic change | S | QuestionImportFileReader.cs:24-27,131-159,239-246 @9d6c596cf |
| H-IMP-02 | #3934 | QuestionImportPlanBuilder (standard v1, legacy adapter, deterministic IDs, parent and condition resolution, plan hash) |
Adapt | DS-20 import-target port; AC-R1a-01, 02; C4 identity; DD-12; R1a-3; T-RD-11 |
Split into a target-neutral rows-to-plan step and target resolution behind IQuestionImportTarget; @question, @system and category placement move into the legacy adapter; errors name the unresolved reference; add the lookup remap or refuse lookups and amend AC-R1a-01 (brief item, §10); filtered-options columns arrive in format v2 for R1a-5 |
M | QuestionImportPlan.cs:32-38,194,224-241,272-290 @9d6c596cf |
| H-IMP-03 | #3934 | QuestionImportService, QuestionImportAuditStore |
Adapt | AC-R1a-01, 03, 04, 08; V2-16; R1a-3, R1a-4; T-RD-11; writer inventory in T-BC-01 |
Expose as LegacyProjectImportTarget; list it in R0's writer inventory and refuse canonical projects; move the bulk-lock check inside the transaction (E-D11); create indexes at start-up (E-D8); rename to pmQuestionImportReceipt and pmQuestionImportAttempt (E-D9); store receipts as BSON (E-D10); add source {kind: file, catalogue or project} for AC-R1a-08; stop calling preview read-only, since it writes an attempt record |
M | QuestionImportService.cs:41-57,96-187 @9d6c596cf |
| H-IMP-04 | #3934 | QuestionImportController, upload processor, annotationQuestionImport flag |
Reuse | C10-T07; flag rules; R1a-3, R1a-4; T-RD-11 |
Regenerate OpenAPI, the client, checksums and flag artefacts when it is re-cut on main; add the routes to EndpointAuthorizationCatalogTests |
S | QuestionImportController.cs:11-110 @9d6c596cf |
| H-IMP-05 | #3934 | Angular question-import component, service, dialog and spec |
Adapt | Plan R1a; U19; C17 and F1c placement; UX §3.12; AC-R1a-07; T-RD-11 |
Validate against U19 and Material 3 and place it as C17 says. Under the canonical adapter, Confirm creates a draft, not live questions. R1a-6 removes the "Focused question" text under the new toolbar | M | question-import.component.ts:35-75,90-240; design.component.html:14-15 @9d6c596cf |
| H-IMP-06 | #3934 | Refusal of unsupported fields (CAPABILITY_NOT_AVAILABLE) |
Reuse | Legacy adapter (T-RD-11); lifted per capability by the R2a canonical adapter (T-RD-02) |
Keep for the legacy target; lift each refusal once C4 version content and AF2 support the field | S | QuestionImportPlan.cs:154-161 @9d6c596cf |
| H-IMP-07 | #3934 | ui-import-implementation.md, how-to/import-question-templates.md |
Adapt | The R1a-3 and R1a-4 PRs; T-RD-11 |
Re-cut to the R1a slices and the import-target port; mark the canonical adapter as an R2a follow-on | S | docs/features/question-management/ui-import-implementation.md @9d6c596cf |
| H-IMP-08 | #2781 | contract-fixtures/annotation-questions-v1.csv and .expected.json |
Adapt | R1a-3 .NET golden test; AC-R1a-01; T-RD-11 |
Map the expected payloads to the .NET plan. #2781 puts option labels into description, which #3934 refuses (E-D18): keep the refusal until display-label persistence lands and record that row as an expected refusal |
S | contract-fixtures/annotation-questions-v1.expected.json:42-52 @2d8b071b0 |
| H-IMP-09 | #2781 | question_template_parser.py and its three test suites |
Reference only | R1a-1 evidence; T-RD-11 |
Compare its cases (cycles, answer_mode, Unicode, sibling order, booleans) with #3934's tests and port gaps as .NET tests; no Python is carried |
S | question_template_parser.py, test_question_template_parser.py @2d8b071b0 |
| H-IMP-12 | #2781 | docs/how-to/bulk-import-annotation-questions.md |
Reference only | Input to H-IMP-07 | Superseded by #3934's how-to | — | docs/how-to/bulk-import-annotation-questions.md @2d8b071b0 |
| H-TREE-01 | #2387 | Child-validity check (checkChildQuestionValidity, isChildInvalid, editor warning) |
Adapt | R1a client warning (T-RD-12); R2a server refusal (T-RD-02); C4; AC-R2a-24; FX-VM-10 |
Compare option IDs, not values (C4; ADR-011's multi-option conditions); a pure function over main's current design.store; the server validator is authoritative; sentence-case copy naming the parent, child and option |
M | QM/design/design.store.ts:153,189-213,444; QM/edit/edit.component.html:43-55 @c81426c |
| H-TREE-02 | #2387 | "New options untouched on children" (OptionStatus.addedUntouched, NewOptionComponent) |
Reference only | AC-R1a-06 (T-RD-12); VM §3.5 added options; C4 |
Re-express as an explicit "Show for new option?" choice keyed on option IDs, with public components; the code overrides private MatOption internals (D-D17) |
S | QM/edit/new-option/new-option.component.ts:39-62 @c81426c |
| H-TREE-07 | #2387 | core/utils/signal.utils.spec.ts |
Adapt | main's core/utils/signal.utils.ts; T-RD-12 |
Keep the computedPrevious cases; rewrite the computedFromPrevious cases to recompute through a source signal (D-D15) |
S | signal.utils.spec.ts:1-50 @c81426c; signal.utils.ts:43-53 @7673ed0 |
| H-DOM-24 | #2572 | ReplacementDraftLineagePlanner, DraftPQS.ReplacePublishedSubtree |
Reference only | ACD §3.4 catalogue copy (copies are new identities with copiedFrom); VM §4.5; T-AC-04 |
The subtree-copy algorithm and its deterministic tests as input; not a versioning mechanism (RD-R30) | — | PMC/Services/ReplacementDraftLineagePlanner.cs:10-70 @af51366 |
5.5 R1b: the Members & groups page (T-AC-11)¶
| ID | PR | Component | Verdict | Target (spec §, contract, release, tracker row) | Adaptation | Effort | Evidence |
|---|---|---|---|---|---|---|---|
| H-GRP-13 | #2224 | custom-groups-table, project-group-badge, membership-table badges, edit-entity toggles, create-project-group, the project-members rework, with specs |
Reference only | R1b page (T-AC-11); R1c (T-AC-10); U8; UX §3.12 |
main rewrote membership-table (#2771, #3459), project-members (#2271) and the invite dialog, so rebuild against U8 and carry the specs' behaviours, not the files |
— | custom-groups-table.component.ts:31-36; project-group-badge.component.ts @fff8385ac |
| H-GRP-14 | #2224 | Web state fixes: the selector drops unknown groups, the stage-permission entity keys on stageId, the effects normalise group objects |
Adapt | AC-R1b-04 (the page shows exactly what is enforced); T-AC-11 |
Reproduce each on main with a failing spec first; main still has all three. The effects change may be unnecessary; confirm whether consumers use id or stageId |
S | project-detail.effects.ts:795-806, membership.selectors.ts:50, stage-permission-set.entity.ts:12 @fff8385ac |
5.6 R1c: configurable groups and the permissions dialog (T-AC-10)¶
| ID | PR | Component | Verdict | Target (spec §, contract, release, tracker row) | Adaptation | Effort | Evidence |
|---|---|---|---|---|---|---|---|
| H-GRP-01 | #2224 | Group endpoints in ProjectController, CreateCustomGroupDto |
Adapt | AC-R1c-01, 12; C10; #3335 WP11; T-AC-10 |
Add rename; ProblemDetails bodies; 404 for an unknown group and 400 for a null body (E-D7); CreatedAtRoute; register in the endpoint catalogue (C10-T07); behind an R1c flag after X-AUTH-SCHEMA; write authorizationAudit entries (AC-R1c-06) |
M | Controllers/ProjectController.cs:772-835,935 @fff8385ac |
| H-GRP-02 | #2224 | CreateCustomGroup, DeleteCustomGroup, TryGetGroup, ProjectMembership.LeaveGroupInternal |
Adapt | C10 anti-escalation and revocation; AC-R1c-08, 10; ACD §3.10; TI-R17; T-AC-10; consumers T-TI-04, T-RS-07 |
Schema 1 only; skip or clear owner-reserved legacy grants without tripping main's storage guard (E-D3); raise GroupCreated, GroupRenamed, GroupDeleted; route revoked Review grants through claim revocation; an active-work impact preview first (T-AC-09); refuse or convert deletion while a training policy or adjudicator assignment references the group; a domain exception, not DuplicateNameException |
M | Security/ProjectSecuritySettings.cs:151-268, ProjectMembership.cs:223-235 @fff8385ac; ProjectPermissionsWithDefaults.cs:29-36 @7673ed0 |
| H-GRP-07 | #2224 | Save changed to await SaveAsync in membership and permission endpoints |
Reference only | Whichever R1c slice touches those endpoints (T-AC-10) |
Hygiene only: its "silent data loss" reason is refuted, because the synchronous save completes before the response | S | ProjectController.cs:1223,1297,1318,1342; MongoUnitOfWorkBase.cs:248-273 @7673ed0 |
| H-GRP-09 | #2224 | ProjectSecuritySettingsTests (20 facts) |
Adapt | AC-R1c-12; Q-09; T-AC-10 |
Schema-1 projects with an Audit; main's security collection scope instead of the global singleton; add cases for an owner-reserved grant during the cascade, rename, events, and deletion refused while a policy references the group |
S | ProjectSecuritySettingsTests.cs:15-500 @fff8385ac |
| H-GRP-10 | #2224 | ProjectControllerTests group section (11 tests plus 2) |
Adapt | AC-R1c-01, 12; C10-T07; T-AC-10 |
main's constructor takes 7 arguments; add endpoint authorisation tests (403 without EditMemberships) on main's harness; ProblemDetails, 404 and 400 expectations; drop the save-count assertions |
M | ProjectControllerTests.cs:30-345 @fff8385ac; ProjectController.cs:69-90 @7673ed0 |
| H-GRP-11 | #2224 | ProjectLevelPermissionTests, ProjectStagePermissionTests, TestPermissionHelper |
Adapt | AC-R1c-02, 05, 09; C10-T02; FX-PERM; X-AUTH-ENFORCE parity; T-AC-10 |
Run every case through the legacy check and ProjectAuthorityEvaluator; enumerate activities from the catalogue (22 project, 4 stage); add the FX-PERM anti-escalation matrix; delete TestPermissionHelper, which sets a process-wide singleton |
M | ProjectLevelPermissionTests.cs:16-167, ProjectStagePermissionTests.cs:17-184 @fff8385ac; ProjectAuthorityEvaluator.cs:213 @7673ed0 |
| H-GRP-12 | #2224 | manage-group-dialog (group × activity matrix, stage accordion, delete) |
Reference only | AC-R1c-07; U8 dialog design; C17; T-AC-10 |
Design and test-case input only: it lists 13 of 22 activities (E-D6) and saves one request per change by client-side read-modify-write (E-D4). R1c needs one atomic group-grants command with a base version and a catalogue-driven dialog | — | manage-group-dialog.component.ts:145-193,310-345,420-470 @fff8385ac |
5.7 R2a: versioned forms and sessions (T-RD-02)¶
| ID | PR | Component | Verdict | Target (spec §, contract, release, tracker row) | Adaptation | Effort | Evidence |
|---|---|---|---|---|---|---|---|
| H-DOM-08 | #2572 | ProjectQuestionSet.Publish, PQSVersion |
Adapt | RD §3.5 form-version pins; VM §4.1; C4; T-RD-02 |
Per form, not a project singleton; add requiredness, minimum instances, ancestor closure, the applicability graph, renderability, standardTarget and ReconciliationPolicy; store in pmAnnotationFormVersion, not in Project |
S | QV/ProjectQuestionSet.cs:167-198 @af51366 |
| H-DOM-13 | #2572 | Candidate validators, QuestionPublishCandidateBuilder, CrossQuestionValidationService, rule codes AQ001 to AQ017 |
Adapt | VM §4.2, §4.3; C4 composition (F1a part in T-RD-01); T-RD-02 |
Build the candidate from a design draft and pinned versions; re-key filters to option IDs; replace A's placement copy with main's placement rules and validator (#2648, #2651); the stage-subset rule becomes ancestor closure; namespace the codes against main's; keep "not renderable" separate. Never run on legacy data during conversion (C-D09) |
M | PMC/Services/Validation/CandidateProjectQuestionSetValidator.cs:21-60,136-182,231-256 @af51366; PMC/Services/QuestionValidationRuleCodes.cs:11-59 |
| H-API-01 | #2574 | QuestionManagementV2Controller (17 endpoints) and its DTOs |
Reference only | RD §3.4 to §3.8, §4.7, §4.8; C4; T-RD-02 (drafts, history, reads); T-RD-05 (impact, publish) |
Rebuild from RD and C4, using the endpoint list as a coverage checklist and its 409 shapes as cases. It publishes by stage, keeps drafts on the question and in Project with no base check (D-D12), puts Optional and Multiple on the question, has untyped option filters, no option IDs and a boolean BreakingChange, and gates by migration status |
— | API/Controllers/QuestionManagementV2Controller.cs:28-30,58-91,114-174,381-449,694-706,763-806 @874a421 |
| H-API-05 | #2574 | MigratedReviewSubmissionService, MigratedReviewMutationPlanner, typed concurrency conflict, ReviewController's migrated branch |
Adapt | RD §4.2 to §4.4; C1, C5, C18; T-RD-02 |
Keep evidence and Study in one transaction, the base-version check with a typed conflict (StaleBase) and the duplicate-key-as-CAS idiom. Drop the statistics write in every save (CR-2), hard deletes, LatestVersion at save time (FX-VM-05) and routing by migration status (C-D10); add the command ledger (E50) |
M | PMC/Services/MigratedReviewSubmissionService.cs:44-203,205-263,273-289 @874a421; API/Controllers/ReviewController.cs:77-90,187-215 |
| H-WEB-03 | #2575 | Design view shell (design-v2.component, design-v2.store) |
Reference only | Design-prototype handoff §4.2, DH-C16, DH-C31; T-RD-02 |
Layout reference only; build inside main's current editor; entity types, not six fixed tabs; index children by parent once, because buildTree is quadratic and the 2,023-question form is an acceptance case |
S | QMV2/design/design-v2.store.ts:44-51,205-218 @0983307 |
| H-WEB-04 | #2575 | Question tree and node | Reference only | AC-R2c-27; AC-R1a-12; UI-1, UI-5, UI-7; handoff §7.4; T-RD-02 |
Add a version chip and a lifecycle state shown by icon shape and text in Material 3 roles; a virtualised CDK tree; the keyboard model from H-WEB-18. The PR has emoji icons, incomplete tree semantics and no arrow keys, and hides the version number | S | QMV2/design/question-tree/question-node.component.ts:97-106; question-node.component.html:10-16 @0983307 |
5.8 F2: the publication contract (T-RD-04)¶
| ID | PR | Component | Verdict | Target (spec §, contract, release, tracker row) | Adaptation | Effort | Evidence |
|---|---|---|---|---|---|---|---|
| H-DOM-01 | #2572 | VersionHistory<T> |
Adapt | RD §3.8; VM §8.3 policy generations in FormVersionIssue; C4; T-RD-04 |
Bounded embedded sequences only. It gives no append-only guarantee, because it wraps a mutable list that a full replace rewrites, so immutability is enforced in the repository and an architecture test; write facade tests | S | QV/VersionHistory.cs:18-58 @af51366 |
| H-DOM-04 | #2572 | PublishDecision, DraftPublishDecision, ChangeImpactClassification, AQVersion.BreakingChange, StagePublishDecisionSummary |
Reference only | RD §3.4, §4.8; VM §3.5, §8.2; C4; T-RD-04 |
Vocabulary only: the classification maps to the publisher's immutable declaration (RD-R20) and the strategy to a treatment; add added, removed and requiredness classes and categories |
— | QV/VersioningValueObjects.cs:101-171 @af51366; QV/AQVersion.cs:65-68 |
| H-DOM-17 | #2572 | TryApplyPublishTransition (Annotation, AnnotationSession, OutcomeData), CreatePublishTransitionReplacement |
Reference only | RD §3.15, §4.8 phase 2; C4, C5; T-RD-04 |
Keep "no version when nothing changes" as the no-generation rows. The code copies status, clears answers and writes without operation, generation or CAS (A-N2, A-N3) | — | PMC/Model/AnnotationAggregate/Annotation.cs:270-341 @af51366; AnnotationSession.cs:253-324 |
| H-SVC-02 | #2573 | StageImpactFingerprint.Compute |
Adapt | RD §4.8 step 3 (PreviewDigestChanged); AC-R2c-06; T-RD-04 |
Digest identities and heads (session ID, head sequence, pinned form version), the draft-only count, the treatments' draft revision and the usage-evidence identity; a canonical or length-prefixed encoding (B-D11); no answer values | S | PMC/Services/AnnotationImpactService.cs:206-245 @2712901 |
| H-SVC-04 | #2573 | SessionTransitionService.PlanStageTransition |
Adapt (rewrite, using the logic as reference) | RD §3.15, §4.8 phase 2; C4 publication; C5 PublicationGenerated; consistency §7.4; T-RD-04, then T-RD-05 |
Keep one combined version per session per generation, publisher attribution, and removed questions leaving the new version. Change: sessions keyed by form across stages; the generation table per category; affected sessions only (B-D5); every prior version (B-D6); head CAS and deterministic IDs per (sessionId, operationId, generation) (B-D7); no invalid Complete; the reviewer stays effective author (B-D9); a pure planner (B-D1). Rewrite the Mouse-to-Rat mapping fixture, a meaning change Q-34 forbids |
L | PMC/Services/SessionTransitionService.cs:36-186 @2712901 |
| H-SVC-05 | #2573 | Decision vocabulary as B consumes it, and the job's decision snapshot | Adapt | IssuePolicyRecord per question in FormVersionIssue; VM §8.2, §8.3; RD-R20, RD-R21; T-RD-04 |
Keep becomes autoUpdate or doNothing; Map becomes a mapping by option ID; ReAnswer becomes requireReanswer, which keeps pins and shows Needs updating; add added, removed, per-category scope, the counting choice and a rationale |
M | QV/VersioningValueObjects.cs:101-160 @3619043; PMC/Model/StageTransitionJobAggregate/StageTransitionJob.cs:259-268 @2712901 |
| H-SVC-09 | #2573 | SessionTransitionService.PromoteDecision |
Reference only | RD §4.7, §4.8 steps 2 and 3; T-RD-04 |
Already implied by attributed design-draft changes; not worth porting | S | PMC/Services/SessionTransitionService.cs:22-34 @2712901 |
| H-SVC-12 | #2573 | StageTransitionInProgressException |
Reference only | Typed PublicationInProgress (D2-11, C18); AC-R2c-14; T-RD-04 |
Key by form; carry the running operation and its progress | S | PMC/Services/StageTransitionExceptions.cs:6-22 @2712901 |
| H-API-02 | #2574 | Publish preconditions (expected published version, expected impact fingerprint, conflict DTO) | Adapt | C4 publication; RD §4.8; D2-11; RD-AE16; T-RD-04, T-RD-05 |
Key on the form head (formId, currentPublishedSeq, publicationSeq); digest over the preview for prior versions, categories and treatments; typed StaleBase and PublicationInProgress; drop the stage job |
S | API/Controllers/QuestionManagementV2Controller.cs:114-174,790-806,920-930 @874a421; PMC/Services/OptimisticConcurrencyExceptions.cs:5-24 |
5.9 R2c: publication with impact (T-RD-05, T-UX-06)¶
| ID | PR | Component | Verdict | Target (spec §, contract, release, tracker row) | Adaptation | Effort | Evidence |
|---|---|---|---|---|---|---|---|
| H-SVC-01 | #2573 | AnnotationImpactService.ComputeStageImpact, IStageImpactReader |
Adapt | RD §4.8 step 1; Q-20; RD-R22; C4; C8; T-RD-05 (with T-RD-04) |
Key by form; count completed, saved incomplete and draft-only sessions by prior form version and route; read a pinned snapshot through the C8 boundary; a set for membership; drop the embedded overload | M | PMC/Services/AnnotationImpactService.cs:23-88; PMC/Interfaces/IStageImpactReader.cs:8-11 @2712901 |
| H-SVC-03 | #2573 | Top-20 answer distribution per question | Reference only | RD §4.8 step 2 mapping choice; Q-34; T-RD-05, T-UX-06 |
Count per option ID, show retired options only, to publishers only; not a code port | S | PMC/Services/AnnotationImpactService.cs:153-162 @2712901 |
| H-SVC-10 | #2573 | StageTransitionSummary and the job counters |
Reference only | VM §8.6 impact manifest; RD §4.8 step 5; T-RD-05 |
Use the manifest vocabulary (carriedForward, needsUpdatingVersion, mappedByPolicy); two counters are never set (B-D10) |
S | PMC/Services/SessionTransitionService.cs:366-381 @2712901 |
| H-API-04 | #2574 | StageImpactReader aggregation pipelines |
Reference only | C4 per-category counts; C8; FX-VM-35; T-RD-05 |
New readers for the per-form-version usage family; this one is stage-keyed, has no draft-only category and no protected usage boundary | — | PMD/Readers/StageImpactReader.cs:27-120 @874a421 |
| H-WEB-06 | #2575 | Impact and mapping panel, DraftPublishDecision |
Reference only | RD §3.4, §4.8; Q-34; handoff §5.9, DH-C32; T-RD-05 |
Copy and the distribution display only; keep becomes doNothing, re-answer becomes requireReanswer, map becomes a per-option mapping where meaning is unchanged. The PR maps by value, uses a binary "may affect" and its effect re-triggers itself (D-D07) |
S | impact-mapping-panel.models.ts:7-28; impact-mapping-panel.component.ts:161-176 @0983307 |
| H-WEB-07 | #2575 | Publish wizard (five-step stepper) | Reference only | RD §4.8; UX §3.9; handoff §5.9; AC-R2c-01 to 35; T-RD-05, T-UX-06 |
Rebuild on the shared impact-preview pattern and main's "Apply anyway" prompts; step titles as sentence-case copy only. It is stage-scoped, throws on open (D-D03), drops decisions (D-D04) and is not zoneless-safe (D-D08) |
S | publish-wizard.component.ts:269-299,336-338,375-387 @0983307 |
| H-WEB-08 | #2575 | Publish concurrency inputs, extractApiErrorMessage |
Adapt | RD §4.8 step 3; UX §3.9, UX-R35; AC-R2c-35; C4, C8; T-RD-05, T-UX-06 |
Rename to the form-head CAS plus the preview digest; route the typed refusal to the recheck screen ("The impact changed since you reviewed it"); typed problem codes instead of parsing message text | S | QMS/qm-v2-root.store.ts:160-175,232-265 @0983307 |
| H-WEB-10 | #2575 | Preview v2 (published versus pending, change markers, banner) | Reference only | AC-R2c-16; T-RD-05 |
Re-implement the markers in main's AF2-based preview against form-version pins, not stage sets |
S | QMV2/preview/preview-v2.store.ts:21,89-133 @0983307 |
| H-WEB-11 | #2575 | Version-transition alert (AF2/version-transition-alert/*) |
Adapt | UX §3.10; RD §4.8 step 5; AC-R2c-07, 16, 20r, 22; T-RD-05 |
Use the copy deck (Needs updating, Outdated answers, Fix); show the generated version's attribution; offer "Use previous answer" only when that value is valid under the session's pinned version; Material 3 roles; host it in main's AF2, which left it out of the lift |
M | AF2/version-transition-alert/version-transition-alert.component.html:1-60; .ts:93-112 @0983307 |
| H-WEB-12 | #2575 | Web AnnotationImpactService and StageImpactSummary DTO |
Reference only | C8; RD §4.8 step 1; T-RD-05 |
The field list as input to the impact-preview DTO; it hard-codes a relative API URL (D-D11) | — | annotation-impact.service.ts:7-28,52-55 @0983307 |
| H-WEB-18 | #2575 | ui-specification.md, publishing-versioning-ux.md, prototype.html, figma-design-reference.md |
Reference only | AC-R2c-27 (T-RD-05); AC-R1a-12 keyboard model (T-RD-12); UX §3.8; handoff §5.9 |
§8's history timeline with a per-field diff is the only design for AC-R2c-27: show the version number on cards, "Restore" creates a new draft, add "Why it changed" and "What reviewers need to do differently". §9's keyboard and tree model serves AC-R1a-12. Already assets A and A2 in the UI coverage comparison | S | docs/features/question-management/ui-specification.md:748-880 @0983307 |
5.10 Conversion track: dry run, staging trials, parity and R6 (T-BC rows)¶
| ID | PR | Component | Verdict | Target (spec §, contract, release, tracker row) | Adaptation | Effort | Evidence |
|---|---|---|---|---|---|---|---|
| H-MIG-01 | #2574 | ProjectQuestionMigrationDomainService and its plan |
Adapt | BC §3.3 baseline structures, §3.4, §4.2 dry-run preview; BC-R15; C4; T-BC-01 (preview), T-BC-03 |
Emit a manifest draft and a legacy activity mapping, not aggregates; target RD's definitions and forms with standardTarget from the effective legacy target and NoAcceptance for target-one forms; pin global system-question versions (H-DOM-07); stable option IDs; deterministic IDs from (project, manifest lineage, legacy ID) with LegacyIdAlias; repairs and validation errors become findings (C-D09). New fixtures: two projects in one database; a project valid since #2648 |
M | PMC/Services/ProjectQuestionMigrationDomainService.cs:71-117,138-144,218-225,246-252 @874a421 |
| H-MIG-04 | #2574 | ReviewStateMigrationDomainService.BuildExtractionBatch |
Adapt | BC §3.2 to §3.4; C1; C14; T-BC-03, T-BC-05 |
Target RD's session, session-version, head and revision records and O1 observations; deterministic IDs; carry the original time, author and wording with legacy-gap states (UnknownLegacyTime, UnknownLegacyAuthor, LegacyCompletionUnvalidated, ValueOrDefaultUnknown); duplicates to a Conflicted head; throws become manifest dispositions; include main's six session fields (C-D06) |
L | PMC/Services/ReviewStateMigrationDomainService.cs:30-66,93-97,142-149,176-201 @874a421 |
| H-MIG-06 | #2574 | MigrationValidationService |
Adapt | BC §3.3 parity report, §4.4; BC-AE13; C16; T-BC-06 |
Compare legacy records with shadow canonical records; persist a parity report per attempt and run; quarantine instead of throwing; legacy-gap differences are expected; add decisions, pools, offered work, permission-filtered output, exports, statistics (#3845) and timings | M | PMC/Services/MigrationValidationService.cs:32-113,197-318 @874a421 |
| H-MIG-07 | #2574 | MigratedStudyReadModelAssembler, ExtractedAnnotationLegacyMapper |
Adapt | BC §4.4, BC-AE13 (exports); domain model §2; T-BC-06 |
Retarget to RD records; carry stored wording, legacy time and author with gap labels, and every current embedded field; missing references become parity findings; for parity and legacy-shaped exports only, retired at R7; never for rollback | M | PMC/Services/MigratedStudyReadModelAssembler.cs:31-142; PMC/Services/ExtractedAnnotationLegacyMapper.cs:29-64,137-164 @874a421 |
| H-DOM-14 | #2572 | AnnotationRelationshipValidator over validation-state projections |
Adapt | BC §4.2 dry-run findings; C16; T-BC-01 |
Rebase on main's validator, keeping #2635's duplicate-ID guard; populate child IDs for extracted answers (A-N9) |
S | PMC/Services/Validation/AnnotationRelationshipValidator.cs:43-60; AnnotationValidationState.cs:32-42 @af51366 |
| H-DOM-22 | #2572 | Extracted OutcomeData, OutcomeDataMutationMapper |
Reference only | C14; BC §3.4 outcome rows; BC-R19; T-BC-05 |
Field inventory only; A copies values verbatim, where BC labels untouched defaults ValueOrDefaultUnknown |
— | PMC/Model/OutcomeDataAggregate/OutcomeData.cs:15-45,382-417 @af51366 |
| H-VAL-02 | #2986 | The conformance rules as an inventory detector | Adapt | BC §3.3 inventory, §8 unmappable values; E37's R6 manifest of unmatched values; T-BC-01 |
A read-only scan of all historical answers, with no grandfathering, reporting counts per rule code into the inventory and manifest | S–M | PMC/Services/Validation/AnnotationAnswerConformanceValidator.cs:142-266 @1c6799b |
5.11 R5a: as-of exports (T-RI-12)¶
| ID | PR | Component | Verdict | Target (spec §, contract, release, tracker row) | Adaptation | Effort | Evidence |
|---|---|---|---|---|---|---|---|
| H-API-11 | #2574 | ExportSpec modes and selectors, legacy-request mapping, the controller gate |
Adapt | C11; AC-R2a-05 previous versions (T-RD-02); AC-R5a-01, 02r, 03 as-of (T-RI-12) |
Modes become Current, PreviousVersions and AsOf (a clock watermark under the C11 as-of rule); selectors become (formId, seq) and session-version IDs, never stage scopes or a raw date; keep the server refusing unsupported modes behind flags |
S | PMC/Model/DataExportJobAggregate/ExportSpec.cs:6-19,72-100; API/Controllers/DataExportController.cs:69-78 @874a421 |
5.12 Later: X1 analysis-ready export (T-RI-08)¶
| ID | PR | Component | Verdict | Target (spec §, contract, release, tracker row) | Adaptation | Effort | Evidence |
|---|---|---|---|---|---|---|---|
| H-API-12 | #2574 | ExportSchemaSidecar, ExportQuestionCatalog, ExportSchemaObservationReader |
Adapt | RI §3.9 codebook (T-RI-08); C11 manifests; the previous-versions manifest in AC-R2a-05 (T-RD-02) |
Key by question and form version; add class, option IDs, requiredness, the version each answer was given under, legacy-gap coverage labels and dataset labels; rebuild the observation reader over canonical revisions | M | PMC/Services/DataExportServices/ExportSchemaSidecar.cs:8-48; PMD/Readers/ExportSchemaObservationReader.cs:30-121 @874a421 |
5.13 Outside the programme¶
| ID | PR | Component | Verdict | Target (spec §, contract, release, tracker row) | Adaptation | Effort | Evidence |
|---|---|---|---|---|---|---|---|
| H-VAL-01 | #2986 | The PR as a legacy-write integrity guard | Reuse (outside the programme, if G0-D6 is confirmed) | Normal triage; T-RD-14 records the disposition |
Wire it into both legacy submission paths, return HTTP 400 with every error, add endpoint and service tests (V-D1, V-D2) | M | PMC/Services/Validation/AnnotationAnswerConformanceValidator.cs:104-397 @1c6799b |
| H-VAL-06 | #2986 | LookupTargetInvalidId rule |
Reuse (outside the programme, with H-VAL-01) | Normal triage | None | S | PMC/Services/Validation/AnnotationRelationshipValidator.cs:248-259 @1c6799b |
| H-VAL-15 | #2812 | Fail-closed boolean flag parsing | Reuse (outside the programme; platform hygiene) | A small separate fix (§5.14) | Port the type check to main's generator, which still returns JSON.parse(value); add a .NET test |
S | src/services/web/scripts/generate-feature-flags.ts @1c38692; same file :389-398 @7673ed0 |
| H-GRP-05 | #2224 | FixAdminGroupIds inverted guard and its notes |
Reference only | Preflight input to #3335's WP-M2; not R1c | Do not change legacy behaviour before migration; record the observation as a preflight query | S | ProjectMembership.cs:266-275, Project.cs:880-887 @fff8385ac |
| H-GRP-06 | #2224 | Schema-0 CustomProjectPermissions getter fix |
Reference only | A follow-up for the authorization programme (§5.14) | Write a failing schema-0 test on main first; the fix follows only if it fails |
S | ProjectSecuritySettings.cs:80 @fff8385ac; ProjectSecuritySettings.cs:60,82, PermissionCollectionWithDefaults.cs:45-47,88-102 @7673ed0 |
5.14 Findings outside this programme¶
Each is a candidate for a follow-up issue. None gets a tracker row.
- Flag parsing (H-VAL-15).
main's generatedparseBooleanreturnsJSON.parse(value), so a non-boolean value such as"1"counts as true (src/services/web/scripts/generate-feature-flags.ts:389-398 @7673ed0). #2812 has the fail-closed check. - Schema-0 permission update (H-GRP-06). On
main,UpdateProjectPermissionon a schema-0 project with no stored overrides probably throwsNotSupportedException, because the schema-0 getter hands out an immutable empty set. It could affect today's chart-visibility dialog. Unverified:main's tests use schema 1, so the first step is a failing schema-0 test. - Members route guard key (WP1d).
project-admin.routes.ts:36 @7673ed0checkseditMembership, while the permission report key iseditMemberships. The G0 dossier's slice R1b-2 and #3335's WP1d already name this fix; the audit confirms it is still onmain. - Extensibility doc drift (H-VAL-13).
main's Approvedannotation-question-extensibility-architecture.md:87-93still lists frozen versus snapshot versions as "not decided". #2812 records Chris's freeze decision of 18 August, and versioning model §3.4 adopts frozen versions.
5.15 Not carried (Avoid)¶
§6.1 groups these by reason.
| ID | PR | Component | Verdict | Target (spec §, contract, release, tracker row) | Adaptation | Effort | Evidence |
|---|---|---|---|---|---|---|---|
| H-DOM-03 | #2572 | AnnotationAnswer.ApplyTransition, AnswerHandlingStrategy, OptionMapping(OldValue, NewValue) |
Avoid | None; RD §3.15 and §4.8 replace it | Not ported. Re-answer keeps pins; mapping writes a new revision by option ID with provenance; it maps null to "" (A-N4). Mapping cases only as fixture inputs |
— | AnnotationAnswer.cs:46-87 @af51366 |
| H-DOM-09 | #2572 | DraftPQS, DraftSQS, DraftQuestion, DraftQuestionTree, DraftContent, DraftSnapshot, class-map lines |
Avoid | None; RD §3.7 replaces it | Not ported; the operation list (add, remove subtree, reorder, promote, withdraw, fork) becomes design-draft change kinds | — | QV/DraftSnapshot.cs:9-84 @af51366; PMC/Model/ProjectAggregate/Project.cs:109-218 |
| H-DOM-12 | #2572 | ADR-012 DRAFT: a question-management entity inside Project with RevertToEmbeddedQuestionModel |
Avoid | None | Not ported; its verb list is input to domain model §6.3 command names | — | docs/decisions/ADR-012-DRAFT-question-management-entity-extraction.md:34-60 @af51366 |
| H-DOM-15 | #2572 | Extracted Annotation, AnnotationVersion, state types, AnnotationMutationMapper |
Avoid | None; C1, C2 and VM §6 replace it | Not ported; factory-fixed identity, the collection-mode invariant and LegacyIdAlias survive as ideas, and 25 tests as C1 test ideas |
— | PMC/Model/AnnotationAggregate/Annotation.cs:15-66,343-368,440-457 @af51366 |
| H-DOM-18 | #2572 | StageQuestionSet, SQSVersion, stage-version references, the Stage.AnnotationQuestions projection |
Avoid | None; SP §3.3 and RD-R31 replace it | Not ported; legacy stage sets are conversion input only (BC-R15) | — | QV/StageQuestionSet.cs:38-150 @af51366; PMC/Model/ProjectAggregate/StageEntity/Stage.cs:128-147 |
| H-DOM-20 | #2572 | MigrationStatus, migrated and rolled-back marks |
Avoid | None; BC §4.9 replaces it | Not ported | — | PMC/Model/ProjectAggregate/Project.cs:221-285 @af51366 |
| H-DOM-21 | #2572 | AnnotationQuestion.CreateExportProjections |
Avoid | None; C11 replaces it | Not ported | — | PMC/Model/ProjectAggregate/AnnotationQuestion.cs:193-283 @af51366 |
| H-DOM-25 | #2461 | The umbrella's domain, service, API and web code | Avoid | None | Nothing to port: 419 of 437 changed paths are byte-identical to a stack tip or main |
— | QV/AnnotationQuestionV2.cs blob 0253500 equals 36190433d @1ca9f5d |
| H-DOM-26 | #2461 | Planning archive and stale copies | Avoid | None | Nothing to port; main keeps the planning context |
— | planning-archive/decisions.md @1ca9f5d; docs/planning/qm-v2-context/README.md @7673ed0 |
| H-SVC-06 | #2573 | Option mapping as executed (ApplyTransition, MapValue) |
Avoid | None; RD-R21 replaces it | Do not carry the mapping test | — | PMC/Model/AnnotationAggregate/AnnotationAnswer.cs:46-85 @3619043 |
| H-SVC-07 | #2573 | Clearing answer and notes on re-answer | Avoid | None; RD §3.15 | Replaced by a generated incomplete version that keeps pins | — | PMC/Services/SessionTransitionService.cs:199-204 @2712901 |
| H-SVC-08 | #2573 | Replacement across question identities and outcome repointing | Avoid | None; RD-R30 | None; context-keyed heads make repointing unnecessary | — | PMC/Services/SessionTransitionService.cs:223-294,296-335 @2712901 |
| H-SVC-11 | #2573 | StageTransitionJob and its repository |
Avoid | None; the publication phase-2 operation in T-RD-04 succeeds it |
Use main's operation family instead |
— | PMC/Model/StageTransitionJobAggregate/StageTransitionJob.cs:10-241 @2712901 |
| H-SVC-13 | #2573 | Review and export locks during a transition | Avoid | None; RD-R35 | None | — | PMC/Services/StageTransitionExceptions.cs:24-60 @2712901 |
| H-VAL-05 | #2986 | Validation against the current definition with grandfathering, for canonical paths | Avoid | None; VM §3.6 | Kept only inside H-VAL-01 for legacy writes | — | PMC/Services/Validation/AnnotationAnswerConformanceValidator.cs:379-397 @1c6799b |
| H-VAL-09 | #2987 | Schema and profile split, per-stage binding, delivery map | Avoid | None | None | — | docs/decisions/ADR-016-annotation-schema-profile-boundary.md:77-98,195-217 @efd4b96 |
| H-VAL-11 | #2629 | FEAT-027 runtime design (boot-time rule endpoint, client rule cache, "ADR-011 Project Template") | Avoid | None | None | — | docs/features/annotation-questions/configurable-validation-strategy.md:286-298 @d761ca4 |
| H-VAL-14 | #2812 | Schema-v0 activation, per-stage AF2 lock, three-service flag and plumbing | Avoid | None | None | — | docs/decisions/ADR-017-annotation-response-modes-and-metadata-contract.md:275-308,349-355 @1c38692 |
| H-MIG-02 | #2574 | System-question documents persisted per project | Avoid | None; VM §3.7 global store | Seed the global store once; conversion pins versions | — | PMC/Services/SystemQuestionFactory.cs:18-46; PMD/Repositories/AnnotationQuestionV2Repository.cs:12-31 @874a421 |
| H-MIG-03 | #2574 | ProjectQuestionMigrationApplicationService, MigrationReport, the project marker |
Avoid | None; BC §4.1 to §4.6 | Only the batch shape (extract, verify, persist, verify) is noted, which BC §4.4 already requires; two flow tests as cases | — | PMA/Services/ProjectQuestionMigrationApplicationService.cs:46-161 @874a421 |
| H-MIG-05 | #2574 | Extracted-state activation (Study.EnableExtractedReviewState, ExtractionInfo.ClearReviewReadModel, the class map) |
Avoid | None; R0's CanonicalScopes marker and reader floor replace it |
Not ported. It erases the legacy originals from pmStudy (C-D05) | — | PMC/Model/StudyAggregate/Study.cs:137,226-253; PMD/Repositories/StudyRepository.cs:2684-2697; PMT/QuestionVersioning/ProjectMigrationMongoIntegrationTests.cs:52-55 @874a421 |
| H-MIG-08 | #2574 | ReconstructiveRollbackService, RollbackProjectMigrationAsync, Study.DisableExtractedReviewState |
Avoid | None; BC §4.9 and §4.10, BC-AE19, BC-AE20 | Not ported. A destructive, lossy hand-back to legacy (C-D07) | — | PMC/Services/ReconstructiveRollbackService.cs:13-16,55-80,105-118 @874a421 |
| H-MIG-09 | #2574 | StudyRepository.BackfillAnnotationVersionIdsAsync, embedded QuestionVersionId |
Avoid | Writer inventory in T-BC-01, as a pattern to refuse |
Not ported | — | PMD/Repositories/StudyRepository.cs:2734-2763 @874a421 |
| H-API-03 | #2574 | StagePublishApplicationService, StagePublishDomainService |
Avoid | None; C4 and RD §4.8 | Not ported | — | PMA/Services/StagePublishApplicationService.cs:62-216 @874a421 |
| H-API-07 | #2574 | ADR-009 pieces copied from #2543, and C's moves into the Application layer | Avoid | None | Not ported | — | PMA/Services/StageReviewService.cs:9-11 @874a421; docs/decisions/ADR-009-domain-vs-application-service-classification.md:67 @7673ed0 |
| H-API-08 | #2574 | StageTransitionWorker, its workload service, readers, repository and background service |
Avoid | None; RD §3.8 and main's ADR-020 pattern |
Not ported | — | PMC/Services/StageTransitionWorker.cs:47-140; API/Services/StageTransitionBackgroundService.cs:19-38 @874a421 |
| H-API-09 | #2574 | ProjectStatsAggregate, ProjectStatsService, ProjectStatsRepository |
Avoid | None; FEAT-024 and C8 | Not ported | — | PMC/Services/ProjectStatsService.cs:21-90 @874a421 |
| H-API-10 | #2574 | Repositories and class maps for PR-A's extracted aggregates | Avoid | None; RD §3.9 and the storage ADR | Not ported | — | PMD/Repositories/AnnotationRepository.cs, AnnotationSessionRepository.cs @874a421 |
| H-API-13 | #2574 | DraftSnapshotService (ageing snapshots in Project) |
Avoid | None; RD §3.7 | Not ported | — | PMC/Services/DraftSnapshotService.cs:8-30 @874a421 |
| H-WEB-01 | #2575 | QmV2RootStore with undo and redo and the draft autosave |
Avoid | None; RD §3.7, §4.7 | Build on main's route-owned editor stores with per-item design-draft changes |
— | QMS/qm-v2-root.store.ts:63-67,126-154,160-175 @0983307 |
| H-WEB-02 | #2575 | Web models and normaliser | Avoid | None; C4 | Its normaliser spec only as a pattern for DTO tests | — | QMS/qm-v2.models.ts:29-33,62-83; qm-v2.normalise.ts:114 @0983307 |
| H-WEB-05 | #2575 | Properties panel | Avoid | None | None | — | properties-panel.component.ts:117-139,168-176 @0983307 |
| H-WEB-09 | #2575 | Assign view and assign tree | Avoid | None; compose forms with main's Assign |
None | — | assign-v2.store.ts:25-34,359-377 @0983307; QM/assign/assign.store.ts:474-488 @7673ed0 |
| H-WEB-13 | #2575 | The AF2 scaffold | Avoid | None | Delete; main has its own AF2 |
— | docs/superpowers/plans/2026-08-06-af2-phase2-foundation.md:82,95,125 @7673ed0 |
| H-WEB-14 | #2575 | Active-reviewer tracking web (32 files) | Avoid | None; for design-page presence, add a group to main's existing hub (UX §3.8) |
None | — | stage/stage-admin/review-settings/review-settings.component.html @71b179c; src/services/web/src/app/study-presence/ @7673ed0 |
| H-WEB-15 | #2575 | Routing, navigation and the newQuestionManagement flag reuse |
Avoid | None; C17 and handoff §4.1 | None | — | project-admin.routes.ts:67-89; project-nav.component.ts:470-520 @0983307 |
| H-WEB-16 | #2575 | release-strategy.md R1 checklist and R1 to R3 boundaries |
Avoid | None; integrated plan §8 | None | — | docs/features/question-management/release-strategy.md:32-123,237-336 @0983307 |
| H-WEB-20 | #2575 | ADRs (two ADR-008s, ADR-009), the QM v2 section of CLAUDE.md, architecture docs, appConfig.local.json |
Avoid | None | None | — | docs/architecture/dependency-map.md:29-65 @0983307 |
| H-TREE-03 | #2387 | Flat assign tree with a sticky header and scroll-synced positions | Avoid | None | None | — | QM/assign/stage-assign/stage-assign.component.ts:48-49,153-240 @c81426c |
| H-TREE-04 | #2387 | Template-driven validation framework | Avoid | None | None | — | QM/edit/edit.form-validations2.ts:21; QM/edit/edit.component.html:6-27 @c81426c |
| H-TREE-05 | #2387 | Node and editor changes gated by annotation count | Avoid | None | None | — | QM/design/question-node/question-node.component.html:61,114 @c81426c |
| H-TREE-06 | #2387 | Navigation and shell changes | Avoid | None | None | — | core/nav/nav.component.html @7673ed0 (+587/−302 since 262d6be) |
| H-TREE-08 | #2387 | Shared utilities (mapFn, KeyMap, MergeUnion, flattenedNodes) |
Avoid | None | None | — | core/actions/util.ts:467-526 @c81426c |
| H-GRP-03 | #2224 | Schema-0 group persistence | Avoid | None; #3335 WP-M2 | Not ported | — | ProjectMembership.cs:85-114, ProjectRepository.cs:427-442,741-744 @fff8385ac; ProjectRepository.cs:1385-1391 @7673ed0 |
| H-GRP-04 | #2224 | ResourceSecurity.json default granting AssignPermissions to Administrators |
Avoid | None; C10 | Not ported; until R1d only the owner assigns grants | — | ResourceSecurity.json:50-53 @fff8385ac; OwnerReservedActivityDefaultsTests.cs:23 @7673ed0 |
| H-GRP-08 | #2224 | PermissionReportResolver null guard |
Avoid | None | Not ported | — | PermissionReportResolver.cs:19-28 @fff8385ac; :30-47 @7673ed0 |
| H-GRP-15 | #2224 | angular.json and vitest.config.ts exclusion edits, AGENTS.md, .gitignore |
Avoid | None | Not ported | — | angular.json:181-184, vitest.config.ts:32-35 @7673ed0 |
| H-IMP-10 | #2781 | Live uploader (bulk_importer.py, generic_uploader.py) |
Avoid | None | Not ported; the hard-coded production base and impersonation identity must not enter the repository | — | bulk_importer.py:33,38,55,284-370 @2d8b071b0 |
| H-IMP-11 | #2781 | pr-tests.yml Python lane and the CLAUDE.md section |
Avoid | None | Drop both; any surviving Python runs on juniper-ci |
— | pr-tests.yml:322-326 @2d8b071b0 |
6. Avoid list¶
6.1 Everything avoided, and why¶
The headline. PR-C's migration erases the embedded legacy annotations, sessions and outcome data from pmStudy (H-MIG-05, C-D05). Its rollback rebuilds legacy records from the extracted ones, keeps only five session fields and migration-time timestamps, deletes the extracted documents and flattens work saved after migration into the legacy model (H-MIG-08, C-D07). Together they break the rule that conversion never modifies or deletes legacy originals (BC-R29), the rule that rollback only restores routing before the first canonical write (BC-R27), the compatibility floor (C16) and AC-M0-04's ban on destructive rollback and hand-back to legacy.
| Reason | Entries | Rules it breaks |
|---|---|---|
| Destructive or lossy migration and rollback | H-MIG-05, H-MIG-08, H-MIG-03, H-MIG-09, H-DOM-20, H-DOM-12 | BC-R25 to BC-R29; BC §4.9, §4.10; C16; AC-M0-04. H-MIG-03 is also non-transactional, non-idempotent and has no caller; H-MIG-09 adds a field inside ExtractionInfo with an unguarded UpdateMany |
| Per-project system-question copies | H-MIG-02 | D2-06, RD-R32, C4: one global store pinned by (guid, systemQuestionVersion, seq). As written only one project can ever migrate (C-D01) |
| Embedded version arrays and the wrong storage shapes | H-DOM-15, H-API-10 | C1, C2, VM §6.3; AC-M0-04 (no unbounded embedded arrays); no context key, entity path or owner scope |
| Single mutable drafts and drafts inside Project | H-DOM-09, H-API-13, H-WEB-01 | OS-A01, D2-11, RD §3.7, RD-R26: many drafts, append-only changes with base revisions, nothing pruned |
| Stage-keyed question sets, publication, transitions and locks | H-DOM-18, H-API-03, H-API-08, H-SVC-11, H-SVC-13, H-WEB-09, H-TREE-03 | SP-R01, RD-R04, RD-R31, RD-R35; consistency §7.2 (one operation family, ADR-020 on main); C4 phase 1 is constant work |
| Answer rewriting that RD forbids | H-DOM-03, H-SVC-06, H-SVC-07, H-SVC-08 | RD-R21 and Q-34 (mapping by option ID, meaning unchanged, originals kept); RD §3.15 (re-answer keeps pins); RD-R30 (same identity) |
| Validation against the live definition | H-VAL-05 | VM §3.6, RD-R07: validity is under the declared, pinned version |
| Exports from the latest version | H-DOM-21 | C11, VM §10.2: exports resolve pinned versions and carry option IDs; it also drops conditions |
| Superseded schema, profile and activation designs | H-VAL-09, H-VAL-11, H-VAL-14 | "Profile" collides with ScreeningProfile (RD §3.6); stages own no form settings (SP §3.2); response modes are canonical content (C4); AF2 only for canonical routes (VB-08) |
| A statistics document written inside saves | H-API-09 | FEAT-024 and ADR-019 supersede it; CR-2 (no per-project document in interactive transactions) |
| Defective or wrong-model designer pieces | H-WEB-02, H-WEB-05, H-WEB-16, H-TREE-04, H-TREE-08 | C4 (option IDs; requiredness on the form); UI-1 to UI-11; AC-R1a-07; AC-R2c-21r |
| #2224 pieces that contradict C10 and #3335 | H-GRP-03, H-GRP-04 | #3335 gate G-D and WP-M2 (schema 1 first); PM2, SEC1 (owner-reserved AssignPermissions); C16 (older binaries drop the groups) |
| #2781's production uploader and CI lane | H-IMP-10, H-IMP-11 | AC-R1a-03 (no partial questions); C3 real-actor provenance; C10; the CI runner rules |
Superseded by main |
H-API-07, H-WEB-13, H-WEB-14, H-WEB-15, H-WEB-20, H-TREE-05, H-TREE-06, H-GRP-08, H-GRP-15, H-DOM-25, H-DOM-26 | §6.3 |
6.2 Confirmed defects not to carry over¶
IDs are the audits' own, prefixed with the audit letter where they would otherwise collide (A-,
C-, D-, E-). Audit B's IDs (B-D…, V-D…) are unchanged.
PR-A (audit A).
| ID | Defect | Evidence @af51366 |
|---|---|---|
| A-N1 | Conditional-parent applicability is lost: the v2 model has no condition field, the embedded-model factory sets filters to null, and C's converter never reads conditions and invents missing parents | QV/AQVersion.cs:10-68; QV/AnnotationQuestionV2.cs:255-286; PMC/Services/ProjectQuestionMigrationDomainService.cs:132-165 @1b93cbb |
| A-N2 | A publish-triggered session version copies the latest status, so it can stay Completed after answers were cleared (against RD-R23) | AnnotationSession.cs:264-277 |
| A-N3 | Re-answer clears the answer instead of keeping the pin and flagging it | AnnotationAnswer.cs:54 |
| A-N4 | Mapping turns a null string answer into "" |
AnnotationAnswer.cs:78-83 |
| A-N5 | No-op detection compares list payloads by reference, so array answers always append a spurious version | Annotation.cs:288-295; AnnotationAnswer.cs:138-174 |
| A-N6 | Unknown legacy types become strings instead of failing closed | AnnotationAnswer.cs:101; Annotation.cs:547 |
| A-N7 | System seed descriptions were edited, so seq 1 would not equal production definitions |
SystemQuestionFactory.cs:119-127 against main's AnnotationQuestion.cs:724,739 |
| A-N8 | Rollback discards all question-set history and allows re-migration from rolled back | Project.cs:221-283 |
| A-N9 | Extracted answers carry no child IDs, so the child-resolution check is skipped | AnnotationValidationState.cs:32-42 |
| A-N10 | Stage.AnnotationQuestions silently switches to the latest stage set for migrated projects |
Stage.cs:128-142 |
| A-N11 | Unbounded embedded version arrays in six documents | AnnotationQuestionV2.cs:119; ProjectQuestionSet.cs:149; StageQuestionSet.cs:77; Annotation.cs:44; AnnotationSession.cs:38; OutcomeData.cs:38 |
The three semantic conflicts are confirmed: A overwrites in-payload duplicate annotation IDs that
2635 rejects, refuses Study questions under custom Study parents that #2648 allows, and treats any¶
system parent as first-level against #2651's anchor split.
PR-B and the validation PRs (audit B).
| ID | Defect | Evidence |
|---|---|---|
| B-D1 | The planner never converts answers to the target type or shape, throws mid-loop after mutating earlier sessions in memory, and so is neither pure nor atomic | PMC/Services/SessionTransitionService.cs:72-147,199-221 @2712901 |
| B-D2 | The job has no failure or stalled state and re-queues forever | StageTransitionJob.cs:10-15,197-204 @2712901 |
| B-D3 | Zombie writer: progress and completion take no lease owner or generation | StageTransitionJob.cs:178-215 @2712901 |
| B-D4 | A random-GUID cursor (forbidden by consistency §7.2) and an ordinal status enum | StageTransitionJob.cs:46,185 @2712901 |
| B-D5 | Every incomplete session gets a new version on any change, even with no affected answer | SessionTransitionService.cs:135-149 @2712901 |
| B-D6 | Blind to prior versions: a session still on v1 receives the v2-to-v3 decisions | SessionTransitionService.cs:72-147 @2712901 |
| B-D7 | No head check and random replacement IDs, so a replay duplicates versions and can overwrite a newer reviewer version | SessionTransitionService.cs:85-90,135-143 @2712901 |
| B-D8 | Unchecked decisions: classification never read, empty mapping silently copies, many-to-one accepted, a meaning-changing fixture | AnnotationAnswer.cs:59-85 @3619043; SessionTransitionServiceTests.cs:39,91 @2712901 |
| B-D9 | The publishing admin becomes the author of the reviewer's answer revisions | SessionTransitionService.cs:206-218 @2712901 |
| B-D10 | Two summary counters are never set | SessionTransitionService.cs:366-381 @2712901 |
| B-D11 | The fingerprint encoding is ambiguous and digests counts, not identities | AnnotationImpactService.cs:30,67,206-245 @2712901 |
| V-D1 | #2986 is unwired: no production caller of its validator exists | git grep @1c6799b; the PR's checklist |
| V-D2 | Its ProblemDetails reports only the first error | AnnotationAnswerConformanceProblemDetails.cs:40-42 @1c6799b |
| V-D3 | #2987 allows a metadata-only response, against C4 and VM §3.4; settle it in E37 | ADR-016…md:110-112 @efd4b96 |
| V-D4 | ADR numbers collide with main (ADR-016, ADR-017 and a planned ADR-011); harvested content takes numbers from ADR-030 to 069 |
git ls-tree origin/main docs/decisions/ @7673ed0 |
PR-C (audit C).
| ID | Defect | Evidence @874a421 unless stated |
|---|---|---|
| C-D01 | Hard-coded system-question GUIDs: a second project or a retry hits a duplicate key and aborts the migration | SystemQuestionFactory.cs:18-46; AnnotationQuestionV2.cs:259; MongoUnitOfWorkBase.cs:355-372 |
| C-D02 | Not transactional: four separate write phases and no session | ProjectQuestionMigrationApplicationService.cs:76-158 |
| C-D03 | Not idempotent: retries fail or short-circuit, and IDs change on every run | ProjectQuestionMigrationApplicationService.cs:54-66; ReviewStateMigrationDomainService.cs:149 |
| C-D04 | No caller: no endpoint, consumer, job or registration | git grep over src @1b93cbb |
| C-D05 | The migration deletes the legacy originals from pmStudy; an older image then reads a migrated Study as having no review data | Study.cs:226-253; StudyRepository.cs:2684-2697; ProjectMigrationMongoIntegrationTests.cs:52-55 |
| C-D06 | Legacy timestamps, question wording and session fields are lost (4 session fields against main's 10) |
Annotation.cs:64-90; ExtractedAnnotationLegacyMapper.cs:144-146,163; AnnotationSession.cs:26-52 @1b93cbb |
| C-D07 | Rollback is destructive and hands the scope back to legacy, flattening later work | ReconstructiveRollbackService.cs:55-80; ProjectQuestionMigrationApplicationService.cs:206-233 |
| C-D08 | The dry run reads no Study, runs read-write and produces no counts | ProjectQuestionMigrationApplicationService.cs:68-74 |
| C-D09 | Conversion invents parents and refuses projects valid since #2648 and #2651, with no quarantine | ProjectQuestionMigrationDomainService.cs:138-144,246-252; CrossQuestionValidationService.cs:316-328 |
| C-D10 | The migrated save path writes statistics inside each save, hard-deletes, resolves the latest version and refuses saves when statistics are missing | MigratedReviewSubmissionService.cs:187-190,239-255,283-288; ProjectStatsService.cs:59-63 |
| C-D11 | An unbounded publish transaction, and an always-on background service with no flag decision | StagePublishApplicationService.cs:168-193; Program.cs:142 @1b93cbb |
| C-D12 | The PR body attributes five assignment-state files to #2467; they are QM's own, so #2467's merge does not supersede them | git log 0d943a34d^2 -- …/ReviewerAssignmentState.cs returns nothing |
PR-D and #2387 (audit D).
| ID | Defect | Evidence |
|---|---|---|
| D-D01 | Undo does nothing (it tracks keys the store lacks), and its Ctrl+Z handler blocks native text undo | QMS/qm-v2-root.store.ts:63-67; design-v2.component.ts:89-99 @0983307 |
| D-D02 | Autosave wipes options[]: the first text edit to a published question saves a draft with no options |
design-v2.component.ts:131-143; QuestionManagementV2Controller.cs:73-86 @0983307 |
| D-D03 | The publish wizard throws NullInjectorError: the store is route-scoped and the dialog opens from the root injector; its spec hides this |
publish-wizard.component.ts:271; assign-v2.component.ts:152,259; spec :77 @0983307 |
| D-D04 | Admin decisions are discarded at four points, from the unbound panel to the wizard payload | design-v2.component.html:32-35; publish-wizard.component.ts:375-387; qm-v2.normalise.ts:114 @0983307 |
| D-D05 | One shared debounce stream drops edits made across questions | QMS/qm-v2-root.store.ts:126-131 @0983307 |
| D-D06 | The properties panel shows published values and reverts typing (static reading) | properties-panel.component.ts:130-139 @0983307 |
| D-D07 | The impact panel's effect re-triggers itself (static reading) | impact-mapping-panel.component.ts:161-176 @0983307 |
| D-D08 | The wizard writes plain fields from an effect in an OnPush component, so it would not refresh with the zoneless flag on and fails main's zoneless discipline; its conflict step always passes |
publish-wizard.component.ts:286-299,336-338 @0983307 |
| D-D09 | Committed conflict markers | docs/architecture/dependency-map.md:29-65 @0983307 |
| D-D10 | A stale revert of main's MongoDB reference doc |
docs/architecture/mongodb-reference.md @0983307 |
| D-D11 | A hard-coded relative API URL and a subscription with no error branch | annotation-impact.service.ts:52-55; assign-v2.component.ts:257 @0983307 |
| D-D12 | PR-C's draft save has no base revision, so the last write wins (against RD-R26) | QuestionManagementV2Controller.cs:58-86 @0983307 |
| D-D13 | #2387's head does not compile: two files sit at the repository root | stage-assign.component.ts:48-49; src/services/web/tsconfig.json:7-18 @c81426c |
| D-D14 | Debug output and TEST: copy in the user-facing editor |
edit.component.html:6-27; edit.form-validations2.ts:21 @c81426c |
| D-D15 | Wrong computedFromPrevious tests |
signal.utils.spec.ts:24-50 @c81426c |
| D-D16 | An operator-precedence bug (annotationCount ?? 0 > 0) |
question-node.component.html:114 @c81426c |
| D-D17 | Private Material internals overridden in a MatOption subclass |
new-option.component.ts:39-62 @c81426c |
| D-D18 | A breaking mapFn signature change and scratch code |
core/actions/util.ts:467-526 @c81426c |
#2224, #3934 and #2781 (audit E).
| ID | Defect | Evidence |
|---|---|---|
| E-D1 | Schema-0 group persistence bypasses WP-M2, activates stray groups on two production documents, and older binaries drop the definitions on save, orphaning membership IDs | ProjectMembership.cs:85-114 @fff8385ac; ProjectRepository.cs:1385-1391 @7673ed0 |
| E-D2 | AssignPermissions granted to Administrators by default fails main's owner-reserved tests |
ResourceSecurity.json:50-53 @fff8385ac; OwnerReservedActivityDefaultsTests.cs:23 @7673ed0 |
| E-D3 | The delete cascade throws on owner-reserved legacy grants under main's storage guard |
ProjectPermissionsWithDefaults.cs:29-36 @7673ed0 |
| E-D4 | The dialog saves grants by non-atomic client-side read-modify-write | manage-group-dialog.component.ts:310-345 @fff8385ac |
| E-D5 | No anti-escalation, audit, notification capture or claim release on group changes | Audit E §3 |
| E-D6 | The activity list covers 13 of 22 grantable project activities | manage-group-dialog.component.ts:145-193 @fff8385ac |
| E-D7 | Wrong status codes and bodies: 400 for an unknown group, an exception for a null body, string bodies | ProjectController.cs:772-835 @fff8385ac |
| E-D8 | #3934 creates indexes on every attempt and swallows failures | QuestionImportService.cs:57 @9d6c596cf |
| E-D9 | Its collection names break the pm{Entity} rule |
docs/architecture/mongodb-reference.md:73-92 @7673ed0 |
| E-D10 | The receipt is stored as a JSON string inside BSON | QuestionImportService.cs @9d6c596cf |
| E-D11 | The bulk-lock check sits outside the transaction | QuestionImportService.cs:96-187 @9d6c596cf |
| E-D12 | Domain logic lives in the API project (ADR-009) | SyRF.API.Endpoint/Services/QuestionImport @9d6c596cf |
| E-D13 | No lookup remap, which AC-R1a-01 promises | QuestionImportPlan.cs @9d6c596cf |
| E-D14 | #2781's uploader fails closed pending answer-label survival and atomic rollback | bulk_importer.py:55 @2d8b071b0 |
| E-D15 | Its live path is non-atomic sequential PUTs with DELETE rollback | bulk_importer.py:284-370 @2d8b071b0 |
| E-D16 | It hard-codes a production base URL and an impersonated investigator (not reproduced here) | bulk_importer.py:33,38 @2d8b071b0 |
| E-D17 | Its CI lane runs on a hosted runner with no valid reason | pr-tests.yml:322-326 @2d8b071b0 |
| E-D18 | Its fixture puts option labels where #3934 refuses distinct labels | annotation-questions-v1.expected.json:42-52 @2d8b071b0 |
Known facts corrected or refuted.
- PR-A's tip has 52 commits after the squash
36190433d, not "about 51": 37 non-merge commits of its own, 7 merges ofmain, and 8 non-merge commits ofmainbrought in by those merges. Audit B's "45 non-merge" counts the 8 frommain; audits A and C's 37 excludes them. Verified on 5 October withgit rev-list --count --no-merges 36190433d..af5136696 ^origin/main. - "Each branch merged main independently" holds for C and D, not B, which is one commit on PR-A's squash (B-R1).
- PR-C embeds 73 files derived from #2467, not 75.
RevertToEmbeddedQuestionModelis not in #2574; it appears only in PR-A's ADR-012 draft.-
2224's "
Save()causes silent data loss" is refuted (H-GRP-07).¶ -
3934's red .NET test is in a project the PR does not touch.¶
6.3 Superseded by main¶
- #2467, active-reviewer tracking, merged on 30 August 2026 (
0d943a34d) with slot reservations and reviewer presence. It supersedes all 73 #2467-derived files in PR-C and PR-D's presence web code (study-presence/since19d266c70). - #2543, the ADR-009 extraction, merged on 27 April 2026 (
6506f7e28). Claims later moved toStudyAssignmentClaim. - AF2. PR-D's scaffold was lifted into
mainas74dffd658(6 August 2026) and has grown to 157 files. - Validators. #2635 (duplicate IDs), #2648 (nested Study parents) and #2651 (anchor split) own the rules PR-A copied.
- Stage capacity fields.
EnforceAnnotationTargetandIdleSessionTimeoutMinutesare onmain, and D2-07 moves them to the form. - Statistics. FEAT-024's
ProjectStatisticsAggregate(ADR-019) supersedespmProjectStats. - Operations. ADR-020's generation-fenced operations supersede the stage-transition job and
worker. The round-2 VB review's harvest of "the
StageTransitionWorkerpattern" pointed at PR-C, not PR-B, and should not be revived. - Write guards. #3909's
IAggregateWriteGuardis the seam R0's ownership guard composes with. - Exports. #3243's export authorisation and formula neutralisation changed the same writers.
- The current new editor. Live assignment locks (#3582, #3594), fresh counts (#3572, #3778), Material 3 colours (#4023), answer-label authoring (#2772, #2739) and the AF2 preview host (#2737). The editor still uses native drag events, which AC-R1a-12 replaces.
- Navigation and shell. #3502, #3454, #3468, #3867, #2712 and #2716;
drawer.scssdeleted. - #2224's neighbours. The permission report (#3642, #3723, #3879), owner-only transfer and owner-reserved refusal (#3964), membership disable (#2271), the members UI rework (#2771, #3459, #3273), new activities (#2788, #3060), the endpoint catalogue tests, and M5b's queued-work authority check.
- #2781's purpose is superseded by #3934 (the extensibility map's step 2g).
- Docs and ADR numbers. #2398 deleted the docs PR-A edits and added
docs/planning/qm-v2-context/. ADR-008 to ADR-012, ADR-016, ADR-017 and ADR-021 are taken onmain.
7. What changed with the owner session¶
7.1 How the owner-session model changes the harvest¶
- The target in the form version (OS-A12, RD-R12). The earlier work kept the target and the
timeout on the stage. Now
FormVersion.standardTargetandReconciliationPolicysit in every canonical form version, and the form owns the timeout and in-progress limit (D2-07). - PR-A's pin guards (H-DOM-08) hang off the form version, not the project.
- PR-C's conversion plan (H-MIG-01) takes the effective legacy target into the v1 form version,
with
NoAcceptancefor target-one forms. - PR-D's stage review settings (H-WEB-14) are avoided.
- Because a target-only change is a publication, the impact counts and digest (H-SVC-01, H-SVC-02) must cover target-only publications and Study overrides.
- Collaborative drafts (OS-A01, D2-11). Every QM v2 draft is one mutable record: PR-A's draft types, PR-C's base-less draft save and PR-D's last-write-wins autosave. Now many drafts exist, each change is appended with its base revision, and presence is never stored. All draft code is avoided (H-DOM-09, H-API-13, H-WEB-01); only PR-A's operation list survives as change kinds. Single-editor drafts with base checks come in R2a, presence in R2c (rollout plan R-AMB-04).
- Generated session versions (D2-01 amended, RD-R23). The round-2 rule said publication writes no evidence, which made PR-B's planner and PR-A's publish transitions wrong in principle. The owner reversed it. PR-B's shape, one combined, attributed version per session per generation, returns (H-SVC-04), and PR-A's transitions become reference (H-DOM-17). The code is still rewritten: it copies status, clears answers, has no CAS or deterministic IDs and makes the admin the author.
- The stage study filter (SP-R01, RD-R04, RD-R31). QM v2 modelled per-stage question sets, stage-keyed publication and stage transitions. Now a stage binds a form identity, its filter alone defines its pool, and sessions are Study × form across stages. The stage sets, transitions, locks, stage-keyed export selectors and per-stage Assign are avoided, and PR-B's counts re-key from stage to form and route.
- Universal faithful conversion (OS-A14, OS-A15). QM v2 assumed an opt-in per-project migration
with a reversible mode and reconstructive rollback (PR-A's
MigrationStatus, PR-C's rollback, PR-D's D-RS-04). Now every project converts to a faithful baseline: originals are never modified (BC-R29), routing rollback applies only before the first canonical write (BC-R27, BC-R28), forward recovery applies after it, and a project that cannot convert faithfully is quarantined (BC-R26). PR-C is the most affected. Its erasure and rollback are avoided, and its planner, extraction, parity checker and projection are adapted with legacy-gap states, deterministic IDs and manifests. The "R6 adapters" of the versioning model move to the parity tooling before pilots (T-BC-06), because R6 now means universal waves. - The consolidated merge (OS-A29). QM v2 predates duplicate merge. Now a merge produces one
current Study with
StudyVersion, tombstones and merge and unmerge session-version kinds. - Any harvested reader or guard (H-DOM-19, H-API-06, H-MIG-07) must respect the tombstone predicate that R0 adds before P2.
- The session-version kinds that replace PR-A's trigger enum (H-DOM-16) include the merge kinds, so the publication planner (H-SVC-04) computes against the latest head, whatever wrote it.
- Replacement across identities (H-SVC-08, H-DOM-24) is not a model for merge lineage, which the duplicate-merge specification defines separately.
- The catalogue (D2-15 amended). Copies carry
copiedFromprovenance and never overwrite the source. #3934's receipt gains asource.kind(H-IMP-03), and PR-A's subtree-copy algorithm becomes a catalogue-copy input (H-DOM-24).
7.2 Versioning model §12.6, row by row¶
Versioning model §12.6 now points to this section; its table is otherwise unchanged.
Harvest column.
| §12.6 item | Still holds? | What changes | Entries |
|---|---|---|---|
VersionHistory<T> |
Yes, narrowed | No append-only guarantee; bounded embedded sequences only (policy generations), never revisions, session versions or draft changes | H-DOM-01 |
Typed AnnotationAnswer payload with EnsureCompatible as the §3.6 validity check |
Yes, adapted | Option IDs, response mode, metadata, fail-closed legacy mapping, structural equality; ApplyTransition moves to Avoid |
H-DOM-02, H-DOM-03 |
ChildQuestionScope as the repeatable identity property |
Yes, corrected | Fixed at creation, not lazily at the first Multiple publish; it covers only half of the shape (AnswerArray decides multi-select) |
H-DOM-06 |
CandidateProjectQuestionSetValidator, CrossQuestionValidationService and AnnotationValidationState as E23 inputs |
Corrected | The first two are composition and content validation (VM §4.2, C4), re-keyed to option IDs, with main's placement rules winning; AnnotationValidationState feeds conversion findings and keeps #2635's guard. E23's fixtures come from #2986. None of them runs on legacy data during conversion |
H-DOM-13, H-DOM-14, H-VAL-03, C-D09 |
SystemQuestionFactory as the §3.7 seed builder |
Yes, adapted | Global records keyed (systemGuid, systemQuestionVersion, seq); main's exact text at seq 1; stable option IDs; a parity test. Per-project copies are avoided |
H-DOM-07, H-MIG-02 |
AnnotationMutationMapper as an R6 adapter |
No | It maps to PR-A's wrong aggregates and drops time and wording; only FromLegacy and the shape resolution carry |
H-DOM-02, H-DOM-06, H-DOM-15 |
ExtractedAnnotationLegacyMapper and MigratedStudyReadModelAssembler as R6 adapters |
Only with fixes | Lossy as written (wording, time, six session fields); for parity and legacy-shaped exports only; the release moves to T-BC-06, before pilots |
H-MIG-07 |
MigrationValidationService as a parity check |
Yes, adapted | Legacy against shadow, a persisted report, quarantine instead of throwing, more dimensions; T-BC-06, before pilots |
H-MIG-06 |
ExportSpec mode reservation renamed to form-version selectors |
Yes | Modes Current, PreviousVersions and AsOf; previous versions in R2a, as-of in R5a |
H-API-11 |
Avoid column.
| §12.6 item | Still holds? | What changes | Entries |
|---|---|---|---|
ReconstructiveRollbackService |
Yes | Joined by the extracted-state activation that erases the originals | H-MIG-08, H-MIG-05 |
RevertToEmbeddedQuestionModel |
Yes, corrected | It is in PR-A's ADR-012 draft, not #2574; PR-C's equivalent is RollbackProjectMigrationAsync |
H-DOM-12, H-MIG-08 |
| Drafts and question-set versions inside the Project document | Yes | Joined by single mutable drafts and ageing snapshots anywhere | H-DOM-09, H-API-13, H-WEB-01 |
AQVersion.PublishDecisions, Optional and Multiple placement |
Yes | The decision vocabulary survives as reference for policy records | H-DOM-04, H-SVC-05, H-WEB-02 |
Untyped AnswerOptionFilters |
Yes | The model also has no condition field, so conditions are lost (A-N1) | H-DOM-05, H-API-01 |
| Unbounded version arrays in annotation and session documents | Yes | Six documents, not two (A-N11) | H-DOM-15, H-API-10 |
| Annotation identity without entity path, owner scope or population | Yes | — | H-DOM-15 |
Stage-keyed export selectors; raw AsOfDate |
Yes | Joined by export projections from the latest version | H-API-11, H-DOM-21 |
ReplacementDraftLineagePlanner unless D2-03 keeps D38 |
Changed | RD-R30 (PROPOSAL) makes a type or multiplicity change an incompatible version of the same identity; the planner survives only as a catalogue-copy algorithm, and replacement across identities is avoided |
H-DOM-24, H-SVC-08 |
Additions.
- Harvest: impact counts and the preview digest (H-SVC-01, H-SVC-02); the planner's shape (H-SVC-04) and treatment vocabulary (H-SVC-05); publish preconditions (H-API-02); the save conflict contract (H-API-05); the form-version pin guards (H-DOM-08); provenance value objects (H-DOM-23); the legacy-write guard pattern (H-DOM-19); the conversion plan builder and review extraction (H-MIG-01, H-MIG-04); the export sidecar (H-API-12); #2986's fixtures, codes and detector (H-VAL-02 to H-VAL-04); #2812's response contract (H-VAL-12); the reviewer alert and digest recheck (H-WEB-11, H-WEB-08).
- Avoid: answer rewriting (H-DOM-03, H-SVC-06 to H-SVC-08); publish transitions that copy status
(A-N2); per-stage question sets and stage pins (H-DOM-18); stage transitions, the job and worker,
and the review and export locks (H-SVC-11, H-SVC-13, H-API-03, H-API-08); per-project system
questions (H-MIG-02); the unguarded backfill (H-MIG-09); statistics writes inside saves and hard
deletes in the save path (H-API-09, C-D10);
MigrationStatus(H-DOM-20); PR-A'sQuestionRefname, which means a version pin there and an identity in the package (H-DOM-23).
8. Sequencing and closure plan¶
8.1 When each harvest happens¶
| When | Slice | Entries | Tracker row | Needs before it starts |
|---|---|---|---|---|
| M0 (M0-5, docs-only) | The QM v2 harvest-and-avoid record, with each adapted and reference entry written into its target row's brief, and the closure notes | Every H-DOM, H-SVC, H-MIG, H-API and H-WEB entry; H-WEB-17, H-WEB-19 | T-RD-10 |
G0, the hold lift, the M0 brief |
| M0 to R0 | Writer and reader inventory and read-only dry-run tooling | H-DOM-06, H-DOM-07 (parity), H-DOM-14, H-MIG-01 (preview), H-VAL-02; refusal patterns from H-MIG-05, H-MIG-09, H-API-06, H-IMP-03 | T-BC-01 |
M0; the F1a part of T-BC-00; no production inventory without its own approval |
| F1a ADR drafts | C4 definitions part, C5, E23, E37, C18, storage and naming ADRs | §5.2 | T-RD-01 |
Final only after M0 go |
| F-O | The C14 ADR | H-VAL-12 (units and metadata types) | T-RI-11 |
The F-O part of T-RI-00; T-SI-01 for event counts |
| R0 | Legacy-writer guard pattern | H-DOM-19, H-API-06 | T-BC-06 (R0 has no row of its own) |
F1a |
| R1a | Import pipeline: R1a-3 preview and R1a-4 legacy apply | H-IMP-01 to H-IMP-09, H-IMP-12 | T-RD-11 |
R1a-1; U19; G0-X9 fixed for R1a-4 |
| R1a | Editor hardening: R1a-5, R1a-6 and the tree-reordering part of AC-R1a-12 | H-TREE-01 (client warning), H-TREE-02, H-TREE-07, H-WEB-18 (keyboard model) | T-RD-12 |
R1a-1; S0-7; G0-X9 fixed |
| R1a | Catalogue copy | H-DOM-24 | T-AC-04 |
ACD §12.5 B5 |
| R1b | Read-only Members & groups page | H-GRP-13, H-GRP-14 | T-AC-11 |
U8 visibility validation |
| R1c | Group CRUD, grants and dialog | H-GRP-01, H-GRP-02, H-GRP-07, H-GRP-09, H-GRP-10, H-GRP-11, H-GRP-12 | T-AC-10 |
X-AUTH-SCHEMA; X-AUTH-ENFORCE or the parity suite; X-AUTH-WP9; Q-03a; X-NOTIF |
| R2a | Versioned forms, sessions, single-editor drafts; the canonical import adapter | §5.7; H-TREE-01 (server refusal); H-IMP-02, H-IMP-03, H-IMP-06 (canonical adapter); H-API-11 (previous versions) | T-RD-02 |
F1a, F1b, F1c; R0's staging rehearsal |
| F2 | Publication contract | §5.8 | T-RD-04 |
The F2 part of T-RD-00 |
| R2c | Publication with impact and the shared impact preview | §5.9 | T-RD-05, T-UX-06 |
F2 |
| Staging trials and parity tooling | Conversion adapters | H-MIG-01, H-MIG-04, H-MIG-06, H-MIG-07, H-DOM-22 | T-BC-03, T-BC-05, T-BC-06 |
R2a and R2b (annotation scope); O1, O2 and AL1 (extraction) |
| R5a | As-of exports | H-API-11 | T-RI-12 |
F6a; R4a |
| X1 | Codebook | H-API-12 | T-RI-08 |
O1, R4c and R5a |
| Outside the programme | #2986 wiring; flag parsing; the schema-0 test; the WP-M2 preflight query | H-VAL-01, H-VAL-06, H-VAL-15, H-GRP-06, H-GRP-05 | None (normal triage; #3335 for H-GRP-05); T-RD-14 records G0-D6 |
G0-D6 for #2986 |
8.2 When each PR can close¶
A PR can close only when all four hold: its harvest slice has merged, G0 has passed, the hold
is lifted, and Chris has answered its disposition item. Until then it stays open and untouched.
GitHub keeps a closed PR's head (refs/pull/<number>/head; checked on 5 October against closed
3969), so the evidence citations in §5 stay readable after closure.¶
| PR group | Disposition item | Harvest done when | Closure row | Earliest point |
|---|---|---|---|---|
| #2461, #2572, #2573, #2574, #2575 | G0-D4 | T-RD-10 merges |
T-RD-13 |
M0-5, before the M0 go or no-go, as G0-D4 recommends |
| #2224 | G0-D5 | T-AC-10 merges |
T-AC-12 |
R1c. The dossier's first recommendation closes it in W0 (§10, item 3) |
| #2986 | G0-D6 | If the deviation is confirmed, this programme does not close it, and only its fixtures and codes are harvested with T-RD-01. If declined, when T-RD-01's E23 corpus merges |
T-RD-14 |
F1a, if declined |
| #2987, #2629, #2812 | G0-D7 | The F1a C4 ADR draft that cites them merges, with #2812's C14 input recorded for T-RI-11 |
T-RD-15 |
F1a ADR drafts |
| #3934, #2781, #2387 | None yet (§10, item 2) | T-RD-11 and T-RD-12 merge |
T-RD-16 |
R1a |
8.3 Order of operations for the out-of-sync QM v2 stack¶
- Never rebase, merge or cherry-pick a stack branch. Every port is a fresh PR from
main, written against the target records. - Read PR-A's tip, not its copies. B, C, D and #2461 carry PR-A's squash
36190433d. PR-A's tipaf5136696adds 37 commits of its own (renames,VersionHistory<T>, init-only fields, the validator split), plus 7 merges ofmain. For any domain type, read the tip. - Isolate each layer's own work against its parent. B is
36190433d..271290115(one commit). C is271290115..874a421fb. D is874a421..71b179c. GitHub's file lists for C and D are dominated by separate main merges; ignore them. - Strip the embedded third-party work. PR-C's 73 #2467-derived files and its #2543 copies are
superseded by those PRs' merges; PR-D's AF2 scaffold and presence code are on
main. The five assignment-state files PR-C attributes to #2467 are QM's own (C-D12). - Check semantic drift before porting anything that validates or writes: #2635, #2648 and
#2651;
main's six extra session fields;IAggregateWriteGuard(#3909); FEAT-024 statistics; ADR-020 operations; #3243 export authorisation. - Port in dependency order: F1a contract parts (
T-RD-01) and the inventory (T-BC-01); then the R0 guard pattern; R2a (T-RD-02); F2 (T-RD-04); R2c (T-RD-05,T-UX-06); and the conversion adapters for staging trials and parity (T-BC-03,T-BC-05,T-BC-06). - Take new ADR numbers from the programme block ADR-030 to 069 (DOM §11.8). The PRs' numbers
(ADR-008 to ADR-012, ADR-016, ADR-017) are all taken on
main. - Close the stack together once
T-RD-10has merged and Chris has answered G0-D4: #2461 with #2572 to #2575 (T-RD-13).
8.4 Tracker notes¶
- R0 has no tracker row. Audits A and C both found this. The guard pattern (H-DOM-19, H-API-06)
is held by
T-BC-06meanwhile. Adding an R0 row is outside this map. - The R1b page had no tracker row. The authorization programme tracks only WP1d (the route guard)
and WP9 (explanations), so the page itself, the G0 dossier's slice R1b-1, now has
T-AC-11. - AC-R1a-12 sits in
T-RD-12because the drag and keyboard work is in the editor tree; the G0 dossier lists it under slice R1a-2. The R1a brief fixes one owner. - The canonical import adapter is part of
T-RD-02(DS-20), not a row of its own.
9. Effort summary¶
Assumptions.
- Each entry carries the audits' grading for the port or adaptation, tests included: S is one day or less, M is one to three days, L is more than three days.
- The central estimate counts S as 1 day, S–M as 1.5, M as 2 and L as 5. The range counts S as 0.5 to 1, S–M as 0.5 to 3, M as 1 to 3 and L as 4 to 8.
- Avoid entries and reference entries graded "—" cost nothing to port. Reading them inside their slices is not counted.
- The estimates exclude the slices that host the ports (
T-RD-02itself, for example), review and CI time, and stack rebases (no PR is rebased; #3934's re-cut onmainis counted in its entries). They also exclude the M0 record (T-RD-10, about one to two days) and the closure notes.
By release.
| Release | Entries | S | S–M | M | L | — | Central (days) | Range (days) |
|---|---|---|---|---|---|---|---|---|
| M0 | 2 | 0 | 0 | 0 | 0 | 2 | 0 | 0 |
| F1a | 15 | 8 | 0 | 3 | 0 | 4 | 14 | 7 to 17 |
| R0 | 2 | 1 | 0 | 0 | 0 | 1 | 1 | 0.5 to 1 |
| R1a | 14 | 8 | 0 | 4 | 0 | 2 | 16 | 8 to 20 |
| R1b | 2 | 1 | 0 | 0 | 0 | 1 | 1 | 0.5 to 1 |
| R1c | 7 | 2 | 0 | 4 | 0 | 1 | 10 | 5 to 14 |
| R2a | 6 | 3 | 0 | 2 | 0 | 1 | 7 | 3.5 to 9 |
| F2 | 9 | 5 | 0 | 1 | 1 | 2 | 12 | 7.5 to 16 |
| R2c | 11 | 7 | 0 | 2 | 0 | 2 | 11 | 5.5 to 13 |
| Conversion track | 7 | 1 | 1 | 3 | 1 | 1 | 13.5 | 8 to 21 |
| R5a | 1 | 1 | 0 | 0 | 0 | 0 | 1 | 0.5 to 1 |
| X1 | 1 | 0 | 0 | 1 | 0 | 0 | 2 | 1 to 3 |
| Outside the programme | 5 | 4 | 0 | 1 | 0 | 0 | 6 | 3 to 7 |
| Not carried (Avoid) | 49 | — | — | — | — | 49 | 0 | 0 |
| Total | 131 | 41 | 1 | 21 | 2 | 66 | 94.5 | 50 to 123 |
By verdict.
| Verdict | Entries | S | S–M | M | L | — | Central (days) | Range (days) |
|---|---|---|---|---|---|---|---|---|
| Reuse | 6 | 5 | 0 | 1 | 0 | 0 | 7 | 3.5 to 8 |
| Adapt | 41 | 18 | 1 | 20 | 2 | 0 | 69.5 | 37.5 to 97 |
| Reference only | 35 | 18 | 0 | 0 | 0 | 17 | 18 | 9 to 18 |
| Avoid | 49 | 0 | 0 | 0 | 0 | 49 | 0 | 0 |
| Total | 131 | 41 | 1 | 21 | 2 | 66 | 94.5 | 50 to 123 |
Inside the programme the central estimate is 88.5 days. The two largest single items are PR-B's planner rewrite (H-SVC-04) and PR-C's review-state extraction (H-MIG-04), both L.
10. Open questions¶
Only owner-level questions are listed. None blocks a brief.
| # | Question | Recommendation | Can it wait? |
|---|---|---|---|
| 1 | G0-D6, with a corrected premise. The dossier keeps #2986 open because "it fixes today's legacy writes". It fixes none yet: nothing calls its validator, and the #2467 blocker has merged. Legacy writes still last until R7, which has no date, and every invalid legacy answer becomes a conversion finding at R6 | Confirm the deviation with this corrected rationale: keep #2986 open outside the programme and treat its wiring (H-VAL-01, H-VAL-06) as a legacy-integrity fix rather than feature work. Chris could then approve that fix separately from the feature hold; this map approves nothing. Harvest its fixtures and codes either way (H-VAL-02 to H-VAL-04). The coordinator updates the dossier | Yes, until the G0 sitting. If Chris wants the integrity fix sooner, it is his separate decision |
| 2 | Who closes #3934, #2781 and #2387, and when? No G0 item covers them; the dossier only says R1a-1 audits them | Add a disposition item: Stream A's lead closes each with its harvest note after its R1a slice merges. #3934 closes as superseded when its split R1a-3 and R1a-4 PRs merge (T-RD-16) |
Yes, until R1a-1 is done |
| 3 | G0-D5 timing for #2224. The dossier's first recommendation closes it in W0 with a harvest note; its alternative keeps it open until R1c | The alternative: keep it open until T-AC-10 merges, as this map's rule says. Closing earlier gains nothing, and GitHub keeps the head either way |
Yes, until the G0 sitting |
Brief items, not owner questions. These go to the named rows' briefs:
- AC-R1a-01 promises a lookup remap that neither import PR has: implement it or refuse lookups and
amend the criterion (
T-RD-11). - A file import should count as a copy for AC-R1a-08, with
source.kind = file(T-RD-11, ACD §12.5 B5). - AC-R1a-04 should say "never changes existing question content", because the legacy writer appends
a new child's ID to its parent (
T-RD-11). - E37 settles whether a metadata-only response is allowed (V-D3,
T-RD-01). - D2-03 decides whether a type or multiplicity change ever needs a new identity, which is the only
case that would revive H-DOM-24 as a versioning mechanism (
T-RD-01). - G0-X9 places R1a-4 to R1a-6 under a gate (
T-G0).