From b898900568f77ade7a1494946634645103088fac Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 17:39:27 +0000 Subject: [PATCH 1/3] Carry retire-class passthroughs; carry-128; species text accepted [openMINDS element leftovers carried] (Jess Haley, 2026-10-29): an openminds_element stating an openminds_id or a fields.description is carried whole as v1, citing carry-128. [species text is the openMINDS equivalent] (Jess Haley, 2026-10-29): the openminds_subject round trip reports a Species' definition and synonym as an accepted difference (spec.same, reported as equivalent), not lost. [one folder, final classes only] (Jess Haley, 2026-10-29): branches that left a v1 document as v1 on purpose, but passed it through silently under its retire class (validated against that V2 file), now carry it whole as v1 with a carried_as_v1 note: daqmetadatareader, daqreader, daqsystem and filenavigator (nothing to declare), electrode_offset_voltage and probe_geometry (nothing to fold), syncgraph (no session), fitcurve and vmspikefit (jModelFit, no subject), hartley_calc (no subject), site2channelmap, stimulus_parameter, vmneuralresponseresiduals and vmspikesummary (always), vmspikefilteringparameters (spiketimes), subjectmeasurement (guards 1-4), openminds_element (no block). No register row covers these reasons, so each marker says none. Branches that feed a batch pass or NDI's second pass, and the reshapes, are unchanged. Tests pinning the old passthroughs are inverted (INVERTED 2026-10-29). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01FxKLA8Tbf9cpvDe7fuVeKf --- .../+did2/+convert/+migrators_j/Contents.m | 8 ++- .../+convert/+migrators_j/daqmetadatareader.m | 9 +++ .../+did2/+convert/+migrators_j/daqreader.m | 9 +++ .../+did2/+convert/+migrators_j/daqsystem.m | 16 ++++- .../+migrators_j/electrode_offset_voltage.m | 9 +++ .../+convert/+migrators_j/filenavigator.m | 9 +++ .../+convert/+migrators_j/hartley_calc.m | 14 +++++ .../+convert/+migrators_j/openminds_element.m | 43 ++++++++++++- .../+convert/+migrators_j/private/jCarryId.m | 15 +++++ .../+convert/+migrators_j/private/jModelFit.m | 16 ++++- .../+convert/+migrators_j/probe_geometry.m | 9 +++ .../+convert/+migrators_j/site2channelmap.m | 14 ++++- .../+migrators_j/stimulus_parameter.m | 24 +++++-- .../+migrators_j/subjectmeasurement.m | 40 ++++++++++++ .../+did2/+convert/+migrators_j/syncgraph.m | 9 +++ .../+migrators_j/vmneuralresponseresiduals.m | 14 ++++- .../+migrators_j/vmspikefilteringparameters.m | 9 +++ .../+convert/+migrators_j/vmspikesummary.m | 14 ++++- .../+convert/+roundtrip/openminds_element.m | 10 +-- .../+convert/+roundtrip/openminds_subject.m | 26 +++++++- tests/+did2/+unittest/testMigratorsJ.m | 49 +++++++++++++++ .../+unittest/testMigratorsJClockAlignment.m | 11 +++- .../testMigratorsJDaqConfiguration.m | 63 ++++++++++++++++--- .../+unittest/testMigratorsJFileNavSoftware.m | 18 ++++-- .../testRawV1RoundTripIngestedEpochs.m | 32 ++++++++-- .../+unittest/testRawV1RoundTripMeasurement.m | 33 +++++++++- .../+did2/+unittest/testRawV1RoundTripProbe.m | 20 +++++- .../+did2/+unittest/testRawV1RoundTripSpike.m | 11 ++++ .../+unittest/testTemplateLiteralTypeTraps.m | 31 +++++---- .../+did2/+unittest/testWholeBatchRoundTrip.m | 60 ++++++++++++++---- 30 files changed, 568 insertions(+), 77 deletions(-) create mode 100644 src/did/+did2/+convert/+migrators_j/private/jCarryId.m diff --git a/src/did/+did2/+convert/+migrators_j/Contents.m b/src/did/+did2/+convert/+migrators_j/Contents.m index 77e7672f..a247544e 100644 --- a/src/did/+did2/+convert/+migrators_j/Contents.m +++ b/src/did/+did2/+convert/+migrators_j/Contents.m @@ -965,8 +965,12 @@ % Deferred: stimulus_bath migrates to a formulation-valued manipulation in the % NDI layer / the coarse resolveDeferredBaths pass. % stimulus_parameter -% Brainstorm-J migrator: did_v1 stimulus_parameter -- a PASSTHROUGH that -% normalises the template's own untypeable placeholder. +% Brainstorm-J migrator: did_v1 stimulus_parameter -- carried whole as v1 +% (since 2026-10-29, [one folder, final classes only]; see the end of the +% function). Until then it was a PASSTHROUGH that normalised the template's +% own untypeable placeholder; the history below explains that repair, which a +% carried document no longer needs (it is not read against V2). A non-empty +% char or a non-numeric `value` still errors. % stimulus_presentation % Brainstorm-J migrator: did_v1 stimulus_presentation -- the decomposition is % DONE BY THE NDI SECOND PASS; pass 1 is a GUARDED PASSTHROUGH that carries diff --git a/src/did/+did2/+convert/+migrators_j/daqmetadatareader.m b/src/did/+did2/+convert/+migrators_j/daqmetadatareader.m index 21daa370..afa0ce80 100644 --- a/src/did/+did2/+convert/+migrators_j/daqmetadatareader.m +++ b/src/did/+did2/+convert/+migrators_j/daqmetadatareader.m @@ -134,6 +134,15 @@ filePattern = jGetChar(blk, 'tab_separated_file_parameter'); if isempty(implClass) && isempty(filePattern) + % [one folder, final classes only] (Jess Haley, 2026-10-29, did-schema + % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no + % v1 document passes through under it, so this document, left as v1 on purpose, + % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, + % 2026-10-24) instead of passing through silently. No carry register row + % covers this reason yet (a question for the team). + % carry-register: none (no reader class and no file parameter, so nothing to declare) + did2.convert.reportNote('carried_as_v1', 'daqmetadatareader', jCarryId(src), ... + 'unplaced: no reader class and no file parameter, so nothing to declare ([passthrough stays v1])'); bodies = {src}; % see THE GUARD above return; end diff --git a/src/did/+did2/+convert/+migrators_j/daqreader.m b/src/did/+did2/+convert/+migrators_j/daqreader.m index 72e50a28..2d63d862 100644 --- a/src/did/+did2/+convert/+migrators_j/daqreader.m +++ b/src/did/+did2/+convert/+migrators_j/daqreader.m @@ -151,6 +151,15 @@ % a body with NEITHER a class name NOR a reader string says nothing at all, % and minting entities from it would be the hollow-document defect. if isempty(implClass) && isempty(readerString) + % [one folder, final classes only] (Jess Haley, 2026-10-29, did-schema + % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no + % v1 document passes through under it, so this document, left as v1 on purpose, + % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, + % 2026-10-24) instead of passing through silently. No carry register row + % covers this reason yet (a question for the team). + % carry-register: none (no reader class and no reader string, so nothing to declare) + did2.convert.reportNote('carried_as_v1', 'daqreader', jCarryId(src), ... + 'unplaced: no reader class and no reader string, so nothing to declare ([passthrough stays v1])'); bodies = {src}; return; end diff --git a/src/did/+did2/+convert/+migrators_j/daqsystem.m b/src/did/+did2/+convert/+migrators_j/daqsystem.m index 57689b4c..96c7b4ea 100644 --- a/src/did/+did2/+convert/+migrators_j/daqsystem.m +++ b/src/did/+did2/+convert/+migrators_j/daqsystem.m @@ -313,7 +313,8 @@ % preserved. The passthrough rescues nothing. % % IT IS LEFT AS IT IS, DELIBERATELY, and pinned by - % testTheResidualPassthroughIsAQuarantinePathByConstruction. NDI sets + % testTheResidualPassthroughIsAQuarantinePathByConstruction (since 2026-10-29 + % testTheResidualDaqsystemIsCarriedWholeAsV1). NDI sets % `ndi_daqsystem_class` on every document it writes and gives every real rig % a filenavigator and a daqreader, so this branch is unreachable for real % data; relaxing the tombstone to admit a hollow document would trade a live @@ -325,6 +326,19 @@ % that can carry a document on its own, via the software_id edge, so it has % to be counted as something-to-declare or a class-name-only daqsystem would % fall into a passthrough that is no longer warranted. + % + % CARRIED, NOT PASSED THROUGH, since 2026-10-29: the quarantine described + % above came from validating the passthrough against the `daqsystem` + % tombstone; a carried document is v1 and is not read against V2. + % [one folder, final classes only] (Jess Haley, 2026-10-29, did-schema + % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no + % v1 document passes through under it, so this document, left as v1 on purpose, + % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, + % 2026-10-24) instead of passing through silently. No carry register row + % covers this reason yet (a question for the team). + % carry-register: none (no daqsystem class, navigator, reader or metadata reader, so nothing to declare) + did2.convert.reportNote('carried_as_v1', 'daqsystem', jCarryId(src), ... + 'unplaced: no daqsystem class, navigator, reader or metadata reader, so nothing to declare ([passthrough stays v1])'); bodies = {src}; return; end diff --git a/src/did/+did2/+convert/+migrators_j/electrode_offset_voltage.m b/src/did/+did2/+convert/+migrators_j/electrode_offset_voltage.m index d9916541..490f5861 100644 --- a/src/did/+did2/+convert/+migrators_j/electrode_offset_voltage.m +++ b/src/did/+did2/+convert/+migrators_j/electrode_offset_voltage.m @@ -121,6 +121,15 @@ offset = finiteScalar(blk, 'offset'); if isempty(offset) + % [one folder, final classes only] (Jess Haley, 2026-10-29, did-schema + % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no + % v1 document passes through under it, so this document, left as v1 on purpose, + % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, + % 2026-10-24) instead of passing through silently. No carry register row + % covers this reason yet (a question for the team). + % carry-register: none (electrode_offset_voltage.offset is not one finite number, so nothing to fold) + did2.convert.reportNote('carried_as_v1', 'electrode_offset_voltage', jCarryId(src), ... + 'unplaced: electrode_offset_voltage.offset is not one finite number, so nothing to fold ([passthrough stays v1])'); bodies = {src}; % nothing to fold -> carry unchanged (discovery fallback) return; end diff --git a/src/did/+did2/+convert/+migrators_j/filenavigator.m b/src/did/+did2/+convert/+migrators_j/filenavigator.m index f478a5e9..65e42580 100644 --- a/src/did/+did2/+convert/+migrators_j/filenavigator.m +++ b/src/did/+did2/+convert/+migrators_j/filenavigator.m @@ -162,6 +162,15 @@ mapList = jFileMatchList(rawField(blk, 'epochprobemap_fileparameters')); if isempty(implClass) && isempty(mapFormat) && isempty(dataList) && isempty(mapList) + % [one folder, final classes only] (Jess Haley, 2026-10-29, did-schema + % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no + % v1 document passes through under it, so this document, left as v1 on purpose, + % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, + % 2026-10-24) instead of passing through silently. No carry register row + % covers this reason yet (a question for the team). + % carry-register: none (no navigator class, file patterns or epoch map, so nothing to declare) + did2.convert.reportNote('carried_as_v1', 'filenavigator', jCarryId(src), ... + 'unplaced: no navigator class, file patterns or epoch map, so nothing to declare ([passthrough stays v1])'); bodies = {src}; % nothing to declare -- see THE GUARD above return; end diff --git a/src/did/+did2/+convert/+migrators_j/hartley_calc.m b/src/did/+did2/+convert/+migrators_j/hartley_calc.m index 05f5fadb..d1c4670f 100644 --- a/src/did/+did2/+convert/+migrators_j/hartley_calc.m +++ b/src/did/+did2/+convert/+migrators_j/hartley_calc.m @@ -397,6 +397,20 @@ % document whole as v1 (carry-100 outside the template, carry-056 inside it), % as it does for an unread calculator field dropped = [dropped, unreadFields(preBody)]; +% NO SUBJECT: jCalculation hands such a document back unchanged (a calculation +% requires its subject). [one folder, final classes only] (Jess Haley, +% 2026-10-29, did-schema V_eta_go_forward_class_audit.md): a retire class's V2 +% file is deleted once no v1 document passes through under it, so it is carried +% whole as v1 with a note here ([passthrough stays v1], Jess Haley, 2026-10-24) +% instead of passing through silently. No carry register row covers this +% reason yet (a question for the team). +if isempty(jSubjectOf(preBody, {'element_id', 'subject_id'})) + % carry-register: none (a hartley_calc with no element_id or subject_id) + did2.convert.reportNote('carried_as_v1', 'hartley_calc', sourceId(preBody), ... + 'unplaced: no element_id or subject_id, so no calculation subject ([passthrough stays v1])'); + bodies = {preBody}; + return; +end bodies = jCalculation(preBody, 'receptive_field_calculation', 'rf', ... 'hartley_calc', 'receptive field', 'ndi.calc.vis.hartley', ... 'ExtraParameters', extra, 'ExtraInputs', inputs, 'DataBody', true, ... diff --git a/src/did/+did2/+convert/+migrators_j/openminds_element.m b/src/did/+did2/+convert/+migrators_j/openminds_element.m index 2fa67d11..4e6c7b22 100644 --- a/src/did/+did2/+convert/+migrators_j/openminds_element.m +++ b/src/did/+did2/+convert/+migrators_j/openminds_element.m @@ -20,7 +20,9 @@ % the asserted `variable`; the ontology id + label are the term `value`. It is % a timeless subject_assertion (no interaction, no session anchor). 1 -> 1. % -% If no openMINDS block is present the document is carried unchanged. +% If no openMINDS block is present, or the block states an `openminds_id` or +% a `fields.description` (carry-128), the document is carried whole as v1 +% with a `carried_as_v1` note. arguments preBody (1,1) struct @@ -33,7 +35,17 @@ block = preBody.openminds_element; end if isempty(block) - v2Body = {preBody}; % no openMINDS block: carry unchanged + % No openMINDS block: nothing to assert, so the document is carried whole + % as v1 ([passthrough stays v1], Jess Haley, 2026-10-24) rather than + % passed through silently under the retire class `openminds_element` + % ([one folder, final classes only], Jess Haley, 2026-10-29, did-schema + % V_eta_go_forward_class_audit.md: a retire class's V2 file is deleted + % once no v1 document passes through under it). No register row covers a + % missing block yet (a question for the team). + % carry-register: none (an openminds_element with no openminds block) + did2.convert.reportNote('carried_as_v1', 'openminds_element', sourceIdOf(preBody), ... + 'unplaced: no openminds block, so no term to assert ([passthrough stays v1])'); + v2Body = {preBody}; return; end @@ -41,6 +53,26 @@ flds = struct(); if isfield(block, 'fields') && isstruct(block.fields); flds = block.fields; end + +% [openMINDS element leftovers carried] (Decided by Jess Haley, 2026-10-29, +% did-schema V_eta_go_forward_class_audit.md): "a v1 `openminds_element` +% holding an `openminds_id` or a `fields.description` is carried whole as v1, +% as for openMINDS subjects ([openMINDS subject leftovers])". The term +% assertion keeps neither, so a document stating either is carried +% (register row carry-128). +omId = strtrim(jGetChar(block, 'openminds_id')); +omDescription = strtrim(jGetChar(flds, 'description')); +if ~isempty(omId) || ~isempty(omDescription) + held = {}; + if ~isempty(omId); held{end+1} = sprintf('openminds_id "%s"', omId); end + if ~isempty(omDescription); held{end+1} = 'fields.description'; end + % carry-register: carry-128 + did2.convert.reportNote('carried_as_v1', 'openminds_element', sourceIdOf(preBody), ... + sprintf(['[carry-128] awaiting_home: %s (the term assertion keeps neither) ' ... + '([openMINDS element leftovers carried])'], strjoin(held, ' and '))); + v2Body = {preBody}; + return; +end node = firstNonEmpty( ... jGetChar(flds, 'preferredOntologyIdentifier'), ... jGetChar(flds, 'ontologyIdentifier'), ... @@ -90,6 +122,13 @@ if isempty(out); out = 'openminds entity'; end end +function id = sourceIdOf(preBody) +%SOURCEIDOF The v1 document's base.id, or '' (did2.convert.v1read.readBase). +b = did2.convert.v1read.readBase(preBody); +id = b.id; +if ~ischar(id); id = ''; end +end + function s = firstNonEmpty(varargin) s = ''; for k = 1:numel(varargin) diff --git a/src/did/+did2/+convert/+migrators_j/private/jCarryId.m b/src/did/+did2/+convert/+migrators_j/private/jCarryId.m new file mode 100644 index 00000000..d34976c4 --- /dev/null +++ b/src/did/+did2/+convert/+migrators_j/private/jCarryId.m @@ -0,0 +1,15 @@ +function id = jCarryId(body) +%JCARRYID The v1 document's base.id for a `carried_as_v1` note ('' when it +% states none), read with did2.convert.v1read.readBase (a legacy +% `ndi_document` identity too). +% +% ID = jCarryId(BODY). +% +% See also did2.convert.reportNote, did2.convert.v1read.readBase. + +b = did2.convert.v1read.readBase(body); +id = b.id; +if ~ischar(id) + id = ''; +end +end diff --git a/src/did/+did2/+convert/+migrators_j/private/jModelFit.m b/src/did/+did2/+convert/+migrators_j/private/jModelFit.m index 0ef81664..3a140fde 100644 --- a/src/did/+did2/+convert/+migrators_j/private/jModelFit.m +++ b/src/did/+did2/+convert/+migrators_j/private/jModelFit.m @@ -16,8 +16,10 @@ % independent_variables[] / dependent_variable (the names), goodness {sse, % sse_per_point}, input_field / output_field (fit_data). % -% PASSES THROUGH (returns {PREBODY}, the v1 tombstone validates it) when -% the document has no subject (decided: "no subject -> refused"). A missing +% CARRIES the document whole as v1 (returns {PREBODY} with a +% `carried_as_v1` note; until 2026-10-29 a silent passthrough the v1 +% tombstone validated) when the document has no subject (decided: "no +% subject -> refused"). A missing % operating system no longer passes it through: `operating_system_id` is % OPTIONAL (Jess Haley, 2026-10-21 [calculation-os-optional]), so the edge is % omitted, never invented. @@ -25,6 +27,16 @@ bodies = {preBody}; subjectId = jSubjectOf(preBody, {'element_id', 'subject_id'}); if isempty(subjectId) + % [one folder, final classes only] (Jess Haley, 2026-10-29, did-schema + % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once + % no v1 document passes through under it, so this document, refused on + % purpose, is carried whole as v1 with a note ([passthrough stays v1], Jess + % Haley, 2026-10-24) instead of passing through silently. No carry register + % row covers this reason yet (a question for the team). + stamp = did2.convert.v1read.readStamp(preBody); + % carry-register: none (a fit with no subject: no element_id or subject_id) + did2.convert.reportNote('carried_as_v1', stamp.class_name, jCarryId(preBody), ... + 'unplaced: no element_id or subject_id, so no calculation subject ([passthrough stays v1])'); return; end [software, swId, execEnv] = jSoftwareFromApp(preBody, 'RequireSession', true); diff --git a/src/did/+did2/+convert/+migrators_j/probe_geometry.m b/src/did/+did2/+convert/+migrators_j/probe_geometry.m index 353c2e90..8b267dac 100644 --- a/src/did/+did2/+convert/+migrators_j/probe_geometry.m +++ b/src/did/+did2/+convert/+migrators_j/probe_geometry.m @@ -201,6 +201,15 @@ end if isempty(obsBodies) + % [one folder, final classes only] (Jess Haley, 2026-10-29, did-schema + % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no + % v1 document passes through under it, so this document, left as v1 on purpose, + % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, + % 2026-10-24) instead of passing through silently. No carry register row + % covers this reason yet (a question for the team). + % carry-register: none (no per-site axis values, so nothing to fold) + did2.convert.reportNote('carried_as_v1', 'probe_geometry', jCarryId(src), ... + 'unplaced: no per-site axis values, so nothing to fold ([passthrough stays v1])'); bodies = {src}; % nothing to fold -> carry unchanged (discovery fallback) return; end diff --git a/src/did/+did2/+convert/+migrators_j/site2channelmap.m b/src/did/+did2/+convert/+migrators_j/site2channelmap.m index 4e01f69c..cd6449e6 100644 --- a/src/did/+did2/+convert/+migrators_j/site2channelmap.m +++ b/src/did/+did2/+convert/+migrators_j/site2channelmap.m @@ -75,8 +75,18 @@ end [~, collision, unplaced] = jRawAccount(preBody, struct('site2channelmap', {{'map'}}), ... {'^probe_id$', '^probe_geometry_id$'}); -jRawCarry('site2channelmap', preBody, collision, unplaced, 'carry-100'); -bodies = {preBody}; % a passthrough, or the carry of the untouched v1 source +if ~jRawCarry('site2channelmap', preBody, collision, unplaced, 'carry-100') + % [one folder, final classes only] (Jess Haley, 2026-10-29, did-schema + % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no + % v1 document passes through under it, so this document, left as v1 on purpose, + % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, + % 2026-10-24) instead of passing through silently. No carry register row + % covers this reason yet (a question for the team). + % carry-register: none (site2channelmap has no V2 home (its map needs the probe_geometry join)) + did2.convert.reportNote('carried_as_v1', 'site2channelmap', jCarryId(preBody), ... + 'unplaced: site2channelmap has no V2 home (its map needs the probe_geometry join) ([passthrough stays v1])'); +end +bodies = {preBody}; % the carry of the untouched v1 source end % ===================== small helpers ======================================= diff --git a/src/did/+did2/+convert/+migrators_j/stimulus_parameter.m b/src/did/+did2/+convert/+migrators_j/stimulus_parameter.m index b38acb60..f24fbbbe 100644 --- a/src/did/+did2/+convert/+migrators_j/stimulus_parameter.m +++ b/src/did/+did2/+convert/+migrators_j/stimulus_parameter.m @@ -1,6 +1,10 @@ function bodies = stimulus_parameter(preBody) -%STIMULUS_PARAMETER Brainstorm-J migrator: did_v1 stimulus_parameter -- a -% PASSTHROUGH that normalises the template's own untypeable placeholder. +%STIMULUS_PARAMETER Brainstorm-J migrator: did_v1 stimulus_parameter -- carried +% whole as v1 (since 2026-10-29, [one folder, final classes only]; see the +% end of the function). Until then it was a PASSTHROUGH that normalised the +% template's own untypeable placeholder; the history below explains that +% repair, which a carried document no longer needs (it is not read against +% V2). A non-empty char or a non-numeric `value` still errors. % % Routed from did2.convert.v1_to_v2 only when TargetVersion == 'V_eta'. % @@ -137,9 +141,10 @@ % empty DOUBLE and validates: `value` is not scalar-constrained). elseif ischar(v) || isstring(v) if isempty(strtrim(char(v))) - % The template's own "unset" placeholder. Absence is the only - % spelling of unset that validates. - preBody.(BLOCK) = rmfield(preBody.(BLOCK), 'value'); + % The template's own "unset" placeholder. It used to be removed + % here so the passthrough validated against the V_eta tombstone; + % the document is now carried whole as v1 (below), which is not + % read against V2, so it is kept as v1 wrote it. else error('did2:convert:stimulusParameterCharValue', ... ['stimulus_parameter.value carries the non-empty char ''%s'' ' ... @@ -155,5 +160,14 @@ end end +% [one folder, final classes only] (Jess Haley, 2026-10-29, did-schema +% V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no +% v1 document passes through under it, so this document, held for the stimulus +% model (#31) and left as v1 on purpose, is carried whole as v1 with a note +% ([passthrough stays v1], Jess Haley, 2026-10-24) instead of passing through +% silently. No carry register row covers it yet (a question for the team). +% carry-register: none (stimulus_parameter has no V2 home until the stimulus model) +did2.convert.reportNote('carried_as_v1', 'stimulus_parameter', jCarryId(preBody), ... + 'unplaced: stimulus_parameter has no V2 home until the stimulus model ([passthrough stays v1])'); bodies = {preBody}; end diff --git a/src/did/+did2/+convert/+migrators_j/subjectmeasurement.m b/src/did/+did2/+convert/+migrators_j/subjectmeasurement.m index e166aa8b..930c7ed4 100644 --- a/src/did/+did2/+convert/+migrators_j/subjectmeasurement.m +++ b/src/did/+did2/+convert/+migrators_j/subjectmeasurement.m @@ -150,6 +150,15 @@ % ---- guard 1: no subject => no observation --------------------------------- subjectId = dependencyValue(preBody, 'subject_id'); if isempty(subjectId) + % [one folder, final classes only] (Jess Haley, 2026-10-29, did-schema + % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no + % v1 document passes through under it, so this document, left as v1 on purpose, + % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, + % 2026-10-24) instead of passing through silently. No carry register row + % covers this reason yet (a question for the team). + % carry-register: none (no subject_id, so no observation) + did2.convert.reportNote('carried_as_v1', 'subjectmeasurement', jCarryId(preBody), ... + 'unplaced: no subject_id, so no observation ([passthrough stays v1])'); return; end @@ -157,6 +166,15 @@ % `measurement` is free text and is the whole of what the statement is ABOUT. name = getCharField(blk, 'measurement'); if isempty(strtrim(name)) + % [one folder, final classes only] (Jess Haley, 2026-10-29, did-schema + % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no + % v1 document passes through under it, so this document, left as v1 on purpose, + % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, + % 2026-10-24) instead of passing through silently. No carry register row + % covers this reason yet (a question for the team). + % carry-register: none (subjectmeasurement.measurement is empty, so no variable) + did2.convert.reportNote('carried_as_v1', 'subjectmeasurement', jCarryId(preBody), ... + 'unplaced: subjectmeasurement.measurement is empty, so no variable ([passthrough stays v1])'); return; end @@ -164,6 +182,15 @@ value = []; if isfield(blk, 'value'); value = blk.value; end if ~(isnumeric(value) && isscalar(value) && isfinite(value)) + % [one folder, final classes only] (Jess Haley, 2026-10-29, did-schema + % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no + % v1 document passes through under it, so this document, left as v1 on purpose, + % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, + % 2026-10-24) instead of passing through silently. No carry register row + % covers this reason yet (a question for the team). + % carry-register: none (subjectmeasurement.value is not one finite number) + did2.convert.reportNote('carried_as_v1', 'subjectmeasurement', jCarryId(preBody), ... + 'unplaced: subjectmeasurement.value is not one finite number ([passthrough stays v1])'); return; end @@ -190,6 +217,19 @@ % ---- guard 4: no honest leaf => carry the document through ------------------ if isempty(folded) + if did2.convert.reportNote('pending', 'carried_as_v1') == 0 + % jMeasurementFold left no note of its own (it writes one, carry-059, + % for a second value): it found no quantity leaf for the name + % [one folder, final classes only] (Jess Haley, 2026-10-29, did-schema + % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no + % v1 document passes through under it, so this document, left as v1 on purpose, + % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, + % 2026-10-24) instead of passing through silently. No carry register row + % covers this reason yet (a question for the team). + % carry-register: none (no quantity kind is named by subjectmeasurement.measurement) + did2.convert.reportNote('carried_as_v1', 'subjectmeasurement', jCarryId(preBody), ... + 'unplaced: no quantity kind is named by subjectmeasurement.measurement ([passthrough stays v1])'); + end return; end bodies = folded; diff --git a/src/did/+did2/+convert/+migrators_j/syncgraph.m b/src/did/+did2/+convert/+migrators_j/syncgraph.m index 6c941c23..4467b1d1 100644 --- a/src/did/+did2/+convert/+migrators_j/syncgraph.m +++ b/src/did/+did2/+convert/+migrators_j/syncgraph.m @@ -167,6 +167,15 @@ sessionId = char(preBody.base.session_id); end if isempty(sessionId) || isEmptySessionSentinel(sessionId) + % [one folder, final classes only] (Jess Haley, 2026-10-29, did-schema + % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no + % v1 document passes through under it, so this document, left as v1 on purpose, + % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, + % 2026-10-24) instead of passing through silently. No carry register row + % covers this reason yet (a question for the team). + % carry-register: none (no session id, so no policy can be anchored) + did2.convert.reportNote('carried_as_v1', 'syncgraph', jCarryId(src), ... + 'unplaced: no session id, so no policy can be anchored ([passthrough stays v1])'); bodies = {src}; % THE GUARD -- see the header return; end diff --git a/src/did/+did2/+convert/+migrators_j/vmneuralresponseresiduals.m b/src/did/+did2/+convert/+migrators_j/vmneuralresponseresiduals.m index 45b4d32b..bcef485a 100644 --- a/src/did/+did2/+convert/+migrators_j/vmneuralresponseresiduals.m +++ b/src/did/+did2/+convert/+migrators_j/vmneuralresponseresiduals.m @@ -92,8 +92,18 @@ 'vmneuralresponseresiduals', {{'element_epochid', 'parameters', 'column_labels', ... 'goodness_of_fit', 'total_power', 'residual_power'}}), ... {'^element_id$'}); -jRawCarry('vmneuralresponseresiduals', preBody, collision, unplaced, 'carry-100'); -bodies = {preBody}; % a passthrough, or the carry of the untouched v1 source +if ~jRawCarry('vmneuralresponseresiduals', preBody, collision, unplaced, 'carry-100') + % [one folder, final classes only] (Jess Haley, 2026-10-29, did-schema + % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no + % v1 document passes through under it, so this document, left as v1 on purpose, + % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, + % 2026-10-24) instead of passing through silently. No carry register row + % covers this reason yet (a question for the team). + % carry-register: none (vmneuralresponseresiduals has no V2 home) + did2.convert.reportNote('carried_as_v1', 'vmneuralresponseresiduals', jCarryId(preBody), ... + 'unplaced: vmneuralresponseresiduals has no V2 home ([passthrough stays v1])'); +end +bodies = {preBody}; % the carry of the untouched v1 source end % ===================== small helpers ======================================= diff --git a/src/did/+did2/+convert/+migrators_j/vmspikefilteringparameters.m b/src/did/+did2/+convert/+migrators_j/vmspikefilteringparameters.m index c0eb18a5..b308fab9 100644 --- a/src/did/+did2/+convert/+migrators_j/vmspikefilteringparameters.m +++ b/src/did/+did2/+convert/+migrators_j/vmspikefilteringparameters.m @@ -161,6 +161,15 @@ % ---------------------------------------------------------------- THE GUARD if hasPayload(blk, 'spiketimes') + % [one folder, final classes only] (Jess Haley, 2026-10-29, did-schema + % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no + % v1 document passes through under it, so this document, left as v1 on purpose, + % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, + % 2026-10-24) instead of passing through silently. No carry register row + % covers this reason yet (a question for the team). + % carry-register: none (vmspikefilteringparameters.spiketimes holds a result, which the method parameters do not keep) + did2.convert.reportNote('carried_as_v1', 'vmspikefilteringparameters', jCarryId(preBody), ... + 'unplaced: vmspikefilteringparameters.spiketimes holds a result, which the method parameters do not keep ([passthrough stays v1])'); bodies = {preBody}; return; end diff --git a/src/did/+did2/+convert/+migrators_j/vmspikesummary.m b/src/did/+did2/+convert/+migrators_j/vmspikesummary.m index ba8637d9..9884307e 100644 --- a/src/did/+did2/+convert/+migrators_j/vmspikesummary.m +++ b/src/did/+did2/+convert/+migrators_j/vmspikesummary.m @@ -103,8 +103,18 @@ 'median_presk_halfwidthmaximum', 'median_postsk_halfwidthmaximum', 'median_max_dvdt', ... 'median_kink_index', 'slope_criterion'}}, 'epochid', {{'epochid'}}), ... {'^element_id$', '^spike_extraction_id$'}); -jRawCarry('vmspikesummary', preBody, collision, unplaced, 'carry-100'); -bodies = {preBody}; % a passthrough, or the carry of the untouched v1 source +if ~jRawCarry('vmspikesummary', preBody, collision, unplaced, 'carry-100') + % [one folder, final classes only] (Jess Haley, 2026-10-29, did-schema + % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no + % v1 document passes through under it, so this document, left as v1 on purpose, + % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, + % 2026-10-24) instead of passing through silently. No carry register row + % covers this reason yet (a question for the team). + % carry-register: none (vmspikesummary has no V2 home) + did2.convert.reportNote('carried_as_v1', 'vmspikesummary', jCarryId(preBody), ... + 'unplaced: vmspikesummary has no V2 home ([passthrough stays v1])'); +end +bodies = {preBody}; % the carry of the untouched v1 source end % ===================== small helpers ======================================= diff --git a/src/did/+did2/+convert/+roundtrip/openminds_element.m b/src/did/+did2/+convert/+roundtrip/openminds_element.m index 5cdb375a..6450a716 100644 --- a/src/did/+did2/+convert/+roundtrip/openminds_element.m +++ b/src/did/+did2/+convert/+roundtrip/openminds_element.m @@ -5,10 +5,12 @@ % [V1, SPEC] = did2.convert.roundtrip.openminds_element(BODIES). BODIES is % the one `assertion` about the element (its `element_id` edge becomes the % assertion's entity_id); private/openmindsInverse says what is rebuilt and -% what is not. Unlike openminds_subject, the migrator converts EVERY -% openminds_element, whether or not it states an `openminds_id`, and writes -% the id nowhere; no decision covers that id or the other openMINDS fields -% for an element, so the round trip reports each one v1 states. +% what is not. A document stating an `openminds_id` or a +% `fields.description` is carried whole as v1 by the migrator (carry-128, +% [openMINDS element leftovers carried], Jess Haley, 2026-10-29), stamped +% did_v1, and never reaches this inverse (did2.convert.roundtrip.check +% compares the carry itself). The other openMINDS fields v1 states (a +% definition, a synonym, ...) are reported. % % See also did2.convert.roundtrip.check, % did2.convert.migrators_j.openminds_element. diff --git a/src/did/+did2/+convert/+roundtrip/openminds_subject.m b/src/did/+did2/+convert/+roundtrip/openminds_subject.m index d78c3d34..572a1080 100644 --- a/src/did/+did2/+convert/+roundtrip/openminds_subject.m +++ b/src/did/+did2/+convert/+roundtrip/openminds_subject.m @@ -8,7 +8,10 @@ % what is not. `matlab_type` is compared as notation ([openMINDS subject % leftovers], Jess Haley, 2026-10-27: "`matlab_type` is notation"): any % openMINDS MATLAB class name ending in the rebuilt type's name states the -% same type. A document the migrator carries (an `openminds_id` that does +% same type. A Species' `definition` and `synonym` are accepted +% differences ([species text is the openMINDS equivalent], Jess Haley, +% 2026-10-29): compared by spec.same, they are reported as `equivalent`, +% not lost. A document the migrator carries (an `openminds_id` that does % not rebuild, a lab-made strain) is stamped did_v1 and never reaches this % inverse (did2.convert.roundtrip.check compares the carry itself). @@ -43,7 +46,28 @@ 'fn', @(a, b) idRebuilds(node, a), 'why', ['the id is rebuilt from the ' ... 'term through the registry''s `equivalent` list ([openMINDS subject ' ... 'leftovers], [term equivalents recorded], Jess Haley, 2026-10-27)']); + if endsWith(v1.openminds.openminds_type, '/Species') + % [species text is the openMINDS equivalent] (Decided by Jess Haley, + % 2026-10-29, did-schema V_eta_go_forward_class_audit.md): "a v1 + % openMINDS subject's species `definition` and `synonym` are not stored + % in V2: the term registry records the openMINDS species instance as + % the term's equivalent ([term equivalents recorded]), and the round + % trip reports the v1 text as an accepted difference". So whatever text + % v1 states there is accepted (reported under `equivalent`, never lost). + why = ['a species'' definition and synonym are openMINDS''s text for the ' ... + 'species instance the term registry records as the term''s equivalent ' ... + '([species text is the openMINDS equivalent], Jess Haley, 2026-10-29)']; + for f = {'definition', 'synonym'} + spec.same(end+1) = struct('path', ['openminds.fields.' f{1}], ... + 'fn', @(a, ~) isText(a), 'why', why); + end + end +end end + +function tf = isText(v) +% the v1 value is text (char or one string): the openMINDS text accepted +tf = ischar(v) || (isstring(v) && isscalar(v)); end function tf = idRebuilds(node, v1Id) diff --git a/tests/+did2/+unittest/testMigratorsJ.m b/tests/+did2/+unittest/testMigratorsJ.m index 0c2e1231..d12e18e0 100644 --- a/tests/+did2/+unittest/testMigratorsJ.m +++ b/tests/+did2/+unittest/testMigratorsJ.m @@ -4964,6 +4964,36 @@ function testOpenmindsElementBecomesTermAssertion(testCase) verifyEqual(testCase, a.document_class.class_name, 'assertion'); % #88 end +function testAnOpenmindsElementWithAnIdOrADescriptionIsCarried(testCase) +% [openMINDS element leftovers carried] (Decided by Jess Haley, 2026-10-29, +% did-schema V_eta_go_forward_class_audit.md): "a v1 `openminds_element` +% holding an `openminds_id` or a `fields.description` is carried whole as v1, +% as for openMINDS subjects"; the register row is carry-128. Each alone carries. +body = struct(); +body.document_class = struct('class_name', 'openminds_element', 'class_version', '1.0.0', ... + 'superclasses', struct('class_name', 'base', 'class_version', '1.0.0')); +body.depends_on = struct('name', {'element_id', 'openminds'}, 'value', {'elem_9', ''}); +body.base = struct('id', 'ome_2', 'session_id', 'sess_09', ... + 'name', '', 'datestamp', '2024-06-01T12:00:00.000Z'); +body.openminds = struct('openminds_type', 'https://openminds.om-i.org/types/CellType', ... + 'matlab_type', 'openminds.controlledterms.CellType', ... + 'openminds_id', 'CellType/dc2f5dd6-5cea-477c-a035-ca2dd3e6649d', ... + 'fields', struct('name', 'type III BNST neuron', ... + 'preferredOntologyIdentifier', 'EMPTY:0000073', 'description', '')); +out = runJ(body); +verifyCarriedWholeAsV1(testCase, body, out); +d = out.summary.migration_notes.carried_as_v1.distinct_details{1}; +verifyTrue(testCase, startsWith(d, '[carry-128] '), d); +desc = body; +desc.base.id = 'ome_3'; +desc.openminds.openminds_id = ''; +desc.openminds.fields.description = 'A type of neuron.'; +out = runJ(desc); +verifyCarriedWholeAsV1(testCase, desc, out); +d = out.summary.migration_notes.carried_as_v1.distinct_details{1}; +verifyTrue(testCase, startsWith(d, '[carry-128] '), d); +end + function testOpenmindsStimulusPassesThroughForSecondPass(testCase) % INVERTED, not updated. This test used to assert the document became a % `term_assertion` whose subject_id came from a `stimulus_id` dependency -- and @@ -5893,6 +5923,25 @@ function testFitcurveWithNoSubjectPassesThroughInsteadOfObservingNobody(testCase verifyEqual(testCase, out{1}.fitcurve.fit_equation, 'gaussian'); end +function testASubjectlessFitIsCarriedWholeAsV1WithANote(testCase) +% [one folder, final classes only] (Decided by Jess Haley, 2026-10-29, did-schema +% V_eta_go_forward_class_audit.md): the guard above no longer passes the fit +% through silently under its retire class; through the pipeline it is carried +% whole as v1 with a `carried_as_v1` note ([passthrough stays v1]). No register +% row covers it yet, so the note cites none. +v1 = struct(); +v1.document_class = struct('class_name', 'fitcurve', 'class_version', '1.0.0', ... + 'superclasses', struct('class_name', {'base'}, 'class_version', {'1.0.0'})); +v1.depends_on = struct('name', {'fit_example_data_id'}, 'value', {''}); +v1.base = struct('id', 'fc_4', 'session_id', 'sess_09', 'name', '', ... + 'datestamp', '2024-06-01T12:00:00.000Z'); +v1.fitcurve = struct('fit_equation', 'gaussian', 'fit_sse', 12.5); +out = runJ(v1); +verifyCarriedWholeAsV1(testCase, v1, out); +d = out.summary.migration_notes.carried_as_v1.distinct_details{1}; +verifyTrue(testCase, contains(d, 'no calculation subject'), d); +end + function testFitcurveWithAnEmptyElementIdIsAlsoGuarded(testCase) % The edge being PRESENT but blank is the same defect as the edge being absent % -- and it is the shape the invented-empty-edge pattern produces, so it is diff --git a/tests/+did2/+unittest/testMigratorsJClockAlignment.m b/tests/+did2/+unittest/testMigratorsJClockAlignment.m index f146bb4d..e8947d86 100644 --- a/tests/+did2/+unittest/testMigratorsJClockAlignment.m +++ b/tests/+did2/+unittest/testMigratorsJClockAlignment.m @@ -371,14 +371,21 @@ function testRuleLessGraphEmitsNoConfigurationEdgeAtAll(testCase) did2.unittest.helpers.sessionDocumentId('sessdoc_1', 'sess_0001')); end -function testGraphWithTheEmptySessionSentinelPassesThrough(testCase) +function testGraphWithTheEmptySessionSentinelIsCarried(testCase) +% INVERTED 2026-10-29 from testGraphWithTheEmptySessionSentinelPassesThrough. +% Decided by Jess Haley, 2026-10-29 [one folder, final classes only] (did-schema +% V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no +% v1 document passes through under it, so the guard now carries the document +% whole as v1 with a `carried_as_v1` note ([passthrough stays v1]). % ndi.session.empty_id() is '0000000000000000_0000000000000000' -- "no specific % session". Pointing a REQUIRED edge at a document that by construction does not -% exist is an orphan, not a fact, so the document passes through instead. +% exist is an orphan, not a fact, so the document is kept as v1 instead. out = runJ(syncgraphFixture({'rule_a'}, '0000000000000000_0000000000000000')); verifyEqual(testCase, numel(out.migrated), 1); verifyEqual(testCase, did2.unittest.helpers.classKey(out.migrated{1}), 'syncgraph'); +verifyEqual(testCase, char(out.migrated{1}.get('document_class.schema_version')), 'did_v1'); verifyEqual(testCase, out.summary.unconverted_count, 1); +verifyTrue(testCase, isfield(out.summary.migration_notes, 'carried_as_v1'), 'the carry is noted'); end function testPolicyNeverEmitsABlankRequiredSessionEdge(testCase) diff --git a/tests/+did2/+unittest/testMigratorsJDaqConfiguration.m b/tests/+did2/+unittest/testMigratorsJDaqConfiguration.m index 9d504620..199b7c9e 100644 --- a/tests/+did2/+unittest/testMigratorsJDaqConfiguration.m +++ b/tests/+did2/+unittest/testMigratorsJDaqConfiguration.m @@ -87,7 +87,9 @@ % passthrough in the first place. The branch is therefore a quarantine path by % construction, unreachable for real data (NDI writes the class name on every % document), and pinned as such by -% testTheResidualPassthroughIsAQuarantinePathByConstruction. +% testTheResidualPassthroughIsAQuarantinePathByConstruction -- INVERTED +% 2026-10-29 to testTheResidualDaqsystemIsCarriedWholeAsV1: the branch now +% carries the body whole as v1 ([one folder, final classes only]). % % THAT CORRECTION IS WHY THE LINE BELOW MATTERS, and it stayed true for % exactly one commit: nothing here had been executed when it was written, and @@ -437,6 +439,31 @@ function testMetadataReaderWithNothingToDeclarePassesThrough(testCase) verifyEqual(testCase, out.summary.unconverted_count, 1); end +function testHollowReadersAndSystemsAreCarriedWithANote(testCase) +% [one folder, final classes only] (Decided by Jess Haley, 2026-10-29, did-schema +% V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no +% v1 document passes through under it. The nothing-to-declare branches above +% (daqreader, daqmetadatareader, daqsystem) still hand the document back +% unchanged, but now as a carry: stamped did_v1, with a `carried_as_v1` note +% ([passthrough stays v1]), not a silent did_v2 passthrough. +v0 = daqsystemV1('', {}); +v0.depends_on = struct('name', {}, 'id', {}); +bodies = {daqreaderV1(''), daqmetadatareaderV1('', ''), v0}; +for k = 1:numel(bodies) + out = runJ(bodies{k}); + verifyEqual(testCase, numel(out.migrated), 1); + label = char(out.migrated{1}.get('document_class.class_name')); + verifyEqual(testCase, char(out.migrated{1}.get('document_class.schema_version')), ... + 'did_v1', [label ': carried']); + verifyTrue(testCase, isfield(out.summary.migration_notes, 'carried_as_v1'), ... + [label ': the carry is noted']); + if isfield(out.summary.migration_notes, 'carried_as_v1') + verifyTrue(testCase, contains(out.summary.migration_notes.carried_as_v1.distinct_details{1}, ... + 'nothing to declare'), label); + end +end +end + % ===================== daqsystem -> acquisition_system ===================== function testDaqsystemClassNameNowFoldsToASoftwareEntity(testCase) @@ -635,8 +662,20 @@ function testDaqsystemClassNameFoldValidatesUnderVEta(testCase) verifyEqual(testCase, classNames(out), {'acquisition_system', 'software'}); end -function testTheResidualPassthroughIsAQuarantinePathByConstruction(testCase) -% INVERTED 2026-08-12, ON ITS FIRST EXECUTION. It was +function testTheResidualDaqsystemIsCarriedWholeAsV1(testCase) +% INVERTED 2026-10-29 from testTheResidualPassthroughIsAQuarantinePathByConstruction, +% which pinned the all-empty daqsystem as a quarantine (1 quarantined, nothing +% surviving). Decided by Jess Haley, 2026-10-29 [one folder, final classes +% only] (did-schema V_eta_go_forward_class_audit.md): a retire class's V2 file +% is deleted once no v1 document passes through under it, so the residual +% branch no longer passes the body through to be validated against the +% `daqsystem` tombstone: it carries it whole as v1 with a `carried_as_v1` +% note ([passthrough stays v1], [generic stage is lossless]). A carried +% document is v1 and is not read against V2, so nothing is quarantined and the +% body survives untouched. That answers the open question the history below +% leaves to the team ("Either it should go, or it should refuse loudly"). +% +% THE EARLIER HISTORY, kept: INVERTED 2026-08-12, ON ITS FIRST EXECUTION. It was % `testDaqsystemTombstoneStillExistsForTheEmptyBody` and asserted % quarantine_count 0 with the body surviving as `daqsystem`. CI says 1 and % nothing survives, and CI is right -- the assertion was written from intent @@ -672,12 +711,18 @@ function testTheResidualPassthroughIsAQuarantinePathByConstruction(testCase) v1 = daqsystemV1('', {}); v1.depends_on = struct('name', {}, 'id', {}); out = runJValidated(v1); -verifyEqual(testCase, out.summary.quarantine_count, 1, ... - ['an all-empty daqsystem is a HOLLOW document: the tombstone requires ' ... - 'two edges and a field it does not have. Quarantine is #37/#38 working, ' ... - 'not a defect -- see this test''s header before changing either side.']); -verifyEmpty(testCase, classNames(out), ... - 'nothing survives, so the passthrough preserves nothing'); +verifyEqual(testCase, out.summary.quarantine_count, 0, ... + 'an all-empty daqsystem is carried whole as v1, never quarantined'); +verifyEqual(testCase, numel(out.migrated), 1, 'one document: the v1 source'); +verifyEqual(testCase, char(out.migrated{1}.get('document_class.schema_version')), ... + 'did_v1', 'carried, not passed through as a V2 document'); +verifyEqual(testCase, char(out.migrated{1}.get('document_class.class_name')), 'daqsystem'); +notes = out.summary.migration_notes; +verifyTrue(testCase, isfield(notes, 'carried_as_v1'), 'the carry is noted'); +if isfield(notes, 'carried_as_v1') + verifyTrue(testCase, contains(notes.carried_as_v1.distinct_details{1}, ... + 'nothing to declare'), notes.carried_as_v1.distinct_details{1}); +end end function testTheFoldsLeaveNoEmptyRequiredEdgeAndNoFragment(testCase) diff --git a/tests/+did2/+unittest/testMigratorsJFileNavSoftware.m b/tests/+did2/+unittest/testMigratorsJFileNavSoftware.m index 2b1df016..b1a40e62 100644 --- a/tests/+did2/+unittest/testMigratorsJFileNavSoftware.m +++ b/tests/+did2/+unittest/testMigratorsJFileNavSoftware.m @@ -151,19 +151,25 @@ function testFilenavigatorEmptyListTextDeclaresNoPatterns(testCase) verifyEqual(testCase, blk.epoch_map_format, 'ndi.epoch.epochprobemap_daqsystem'); end -function testFilenavigatorWithNothingToDeclarePassesThrough(testCase) -% THE GUARD (the fitcurve / openminds_stimulus / probe_geometry shape). All four -% source fields empty means an epoch_file_pattern with no patterns, no format and -% no software edge -- a hollow document that validates clean and says nothing. -% The source class keeps its V_eta tombstone, so passing through loses nothing -% and shows up in `unconverted_by_class` where it can be counted. +function testFilenavigatorWithNothingToDeclareIsCarried(testCase) +% INVERTED 2026-10-29 from testFilenavigatorWithNothingToDeclarePassesThrough, +% which relied on the source class keeping its V_eta tombstone. Decided by Jess +% Haley, 2026-10-29 [one folder, final classes only] (did-schema +% V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no +% v1 document passes through under it, so the guard now carries the document +% whole as v1 with a `carried_as_v1` note ([passthrough stays v1]). +% THE GUARD: all four source fields empty means an epoch_file_pattern with no +% patterns, no format and no software edge -- a hollow document. It is still +% counted in `unconverted_by_class` (handed back unchanged). v1 = navBody('', '', ''); v1.filenavigator.ndi_filenavigator_class = ''; out = runJ(v1); verifyEmpty(testCase, out.quarantine); verifyEqual(testCase, numel(out.migrated), 1); verifyEqual(testCase, did2.unittest.helpers.classKey(out.migrated{1}), 'filenavigator'); +verifyEqual(testCase, char(out.migrated{1}.get('document_class.schema_version')), 'did_v1'); verifyEqual(testCase, out.summary.unconverted_count, 1); +verifyTrue(testCase, isfield(out.summary.migration_notes, 'carried_as_v1'), 'the carry is noted'); end function testFilenavigatorWithoutImplementationClassOmitsTheEdge(testCase) diff --git a/tests/+did2/+unittest/testRawV1RoundTripIngestedEpochs.m b/tests/+did2/+unittest/testRawV1RoundTripIngestedEpochs.m index 2b605f7e..4fd74cb9 100644 --- a/tests/+did2/+unittest/testRawV1RoundTripIngestedEpochs.m +++ b/tests/+did2/+unittest/testRawV1RoundTripIngestedEpochs.m @@ -25,8 +25,10 @@ % with edges set % No corpus of the four zips holds any of them. % -% All four pass through pass 1 unchanged, as before; each round trips as -% itself. No known findings. +% The two readers pass through pass 1 unchanged, as before (resolveEpochProbemap +% folds their files); vmneuralresponseresiduals and vmspikesummary, which no +% pass reads, are carried whole as v1 since 2026-10-29 ([one folder, final +% classes only], Jess Haley). Each round trips as itself. No known findings. tests = functiontests(localfunctions); end @@ -40,17 +42,35 @@ function testTheSwitchListsTheFamily(testCase) verifyEqual(testCase, r.summary.legacy_ndi_document.bodies_read_raw_v1, size(f, 1)); end -function testFixturesRoundTripAsPassthroughs(testCase) +function testFixturesRoundTripAsPassthroughsOrCarries(testCase) +% INVERTED 2026-10-29 from testFixturesRoundTripAsPassthroughs, which asserted +% all four were silent pass-1 passthroughs stamped did_v2. Decided by Jess +% Haley, 2026-10-29 [one folder, final classes only] (did-schema +% V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no +% v1 document passes through under it. vmneuralresponseresiduals and +% vmspikesummary (no V2 home, read by no later pass) are now carried whole as +% v1 with a note ([passthrough stays v1]); the two readers still pass through +% for resolveEpochProbemap, which folds their files. f = fixtures(); for k = 1:size(f, 1) [label, v1] = f{k, :}; out = runRaw(v1); verifyEmpty(testCase, out.quarantine, label); verifyEqual(testCase, numel(out.passthrough), 1, [label ': a pass-1 passthrough']); - verifyEqual(testCase, char(out.migrated{1}.get('document_class.schema_version')), ... - 'did_v2', [label ': a passthrough is not a carry']); + isCarry = any(strcmp(v1.document_class.class_name, ... + {'vmneuralresponseresiduals', 'vmspikesummary'})); + if isCarry + verifyEqual(testCase, char(out.migrated{1}.get('document_class.schema_version')), ... + 'did_v1', [label ': carried whole as v1']); + [carried, notes] = did2.unittest.helpers.carryNote({v1}); + verifyTrue(testCase, carried, [label ': carried as the untouched v1 source']); + verifyTrue(testCase, contains(notes, 'has no V2 home'), notes); + else + verifyEqual(testCase, char(out.migrated{1}.get('document_class.schema_version')), ... + 'did_v2', [label ': a passthrough is not a carry']); + end r = did2.convert.roundtrip.check(v1, out.migrated); - verifyFalse(testCase, r.carried, label); + verifyEqual(testCase, r.carried, isCarry, label); did2.unittest.helpers.verifyRoundTrip(testCase, r, label, {}); end end diff --git a/tests/+did2/+unittest/testRawV1RoundTripMeasurement.m b/tests/+did2/+unittest/testRawV1RoundTripMeasurement.m index d45a0ca5..8c235e05 100644 --- a/tests/+did2/+unittest/testRawV1RoundTripMeasurement.m +++ b/tests/+did2/+unittest/testRawV1RoundTripMeasurement.m @@ -27,8 +27,11 @@ % the renamed path it passed through as a V2 `measurement`, a `retire` class, % which NDI's last step stored as v1: the same end state). subjectmeasurement % converts with its datestamp as an absolute time reference. -% stimulus_parameter is kept as it is (its decided target is built by no -% migrator yet). No known findings. +% stimulus_parameter is carried whole as v1 (its decided target is built by +% no migrator yet; INVERTED 2026-10-29 from "kept as it is", a silent +% pass-1 passthrough: [one folder, final classes only], Jess Haley, +% 2026-10-29, deletes a retire class's V2 file once no v1 document passes +% through under it). No known findings. tests = functiontests(localfunctions); end @@ -76,6 +79,30 @@ function testADateIsCarriedNotPassedThrough(testCase) verifyTrue(testCase, contains(notes, 'no V2 statement'), notes); end +function testAStimulusParameterIsCarriedNotPassedThrough(testCase) +% [one folder, final classes only] (Jess Haley, 2026-10-29, did-schema +% V_eta_go_forward_class_audit.md): carried whole as v1 with a note +% ([passthrough stays v1]); no register row covers it yet +[carried, notes] = did2.unittest.helpers.carryNote({stimulusParameter()}); +verifyTrue(testCase, carried); +verifyTrue(testCase, contains(notes, 'stimulus_parameter has no V2 home'), notes); +end + +function testASubjectMeasurementItCannotStateIsCarried(testCase) +% the guards that used to pass the document through silently now carry it +% with a note ([one folder, final classes only], Jess Haley, 2026-10-29) +v1 = subjectAge(); +v1.depends_on = struct('name', 'subject_id', 'value', ''); +[carried, notes] = did2.unittest.helpers.carryNote({v1}); +verifyTrue(testCase, carried, 'no subject'); +verifyTrue(testCase, contains(notes, 'no subject_id'), notes); +v1 = subjectAge(); +v1.subjectmeasurement.measurement = ''; +[carried, notes] = did2.unittest.helpers.carryNote({v1}); +verifyTrue(testCase, carried, 'no name'); +verifyTrue(testCase, contains(notes, 'subjectmeasurement.measurement is empty'), notes); +end + function testTheConvertedDocumentsValidate(testCase) f = fixtures(); for k = 1:size(f, 1) @@ -129,7 +156,7 @@ function testAnUnplacedDocumentFailsTheRoundTrip(testCase) 'measurement, a term', measurementTerm(), 'converted'; ... 'measurement, a date', measurementDate(), 'carried'; ... 'subjectmeasurement, age with a datestamp', subjectAge(), 'converted'; ... - 'stimulus_parameter, temptable2stimulusparameters', stimulusParameter(), 'kept'}; + 'stimulus_parameter, temptable2stimulusparameters', stimulusParameter(), 'carried'}; % INVERTED 2026-10-29 from 'kept' end function d = writerMeasurement(id, node, name, stringValue, numericValue) diff --git a/tests/+did2/+unittest/testRawV1RoundTripProbe.m b/tests/+did2/+unittest/testRawV1RoundTripProbe.m index 7884d822..01f49826 100644 --- a/tests/+did2/+unittest/testRawV1RoundTripProbe.m +++ b/tests/+did2/+unittest/testRawV1RoundTripProbe.m @@ -32,8 +32,13 @@ % ([probe contact geometry carried]). The same document % with no shank_id (the "converted shape", DID-side) % converts and loses nothing. -% site2channelmap a pass-1 passthrough (the NDI second pass joins it to -% its geometry); it round trips as itself. +% site2channelmap carried whole as v1 (no V2 home; nothing joins it to +% its geometry yet). INVERTED 2026-10-29 from "a pass-1 +% passthrough": [one folder, final classes only] (Jess +% Haley, 2026-10-29) deletes a retire class's V2 file once +% no v1 document passes through under it, so the +% migrator now carries it with a note. It round trips as +% itself. % electrode_offset_voltage converts; with a temperature and with the % writer's NaN. Nothing lost. % No writer-shaped document loses a field: there are no known findings for @@ -157,6 +162,15 @@ function testAFieldTheMigratorDoesNotReadCarriesTheDocument(testCase) verifyCarried(testCase, v1, 'site2channelmap.headstage'); end +function testAnOffsetThatIsNotANumberIsCarriedWithANote(testCase) +% [one folder, final classes only] (Jess Haley, 2026-10-29, did-schema +% V_eta_go_forward_class_audit.md): the "nothing to fold" branch used to pass +% the document through silently under the retire class; it is now carried +% whole as v1 with a `carried_as_v1` note ([passthrough stays v1]) +v1 = writerOffset(NaN, 21.5); +verifyCarried(testCase, v1, 'electrode_offset_voltage.offset is not one finite number'); +end + function testTheWriterGeometryCarriesItsShanks(testCase) [carried, notes] = did2.unittest.helpers.carryNote({writerGeometry()}); verifyTrue(testCase, carried); @@ -237,7 +251,7 @@ function testAnUnplacedDocumentFailsTheRoundTrip(testCase) f = { ... 'probe_geometry, fromStruct', writerGeometry(), 'carried'; ... 'probe_geometry, converted shape', convertedGeometry(), 'converted'; ... - 'site2channelmap', writerSiteMap(), 'passthrough'; ... + 'site2channelmap', writerSiteMap(), 'carried'; ... % INVERTED 2026-10-29 from 'passthrough' ([one folder, final classes only]) 'electrode_offset_voltage, with a temperature', writerOffset(0.012, 21.5), 'converted'; ... 'electrode_offset_voltage, temperature not recorded (null)', writerOffset(-0.0031, NaN), 'converted'; ... 'electrode_offset_voltage, temperature not recorded (NaN)', nanTemp, 'converted'}; diff --git a/tests/+did2/+unittest/testRawV1RoundTripSpike.m b/tests/+did2/+unittest/testRawV1RoundTripSpike.m index cc94adf6..5c4bda0a 100644 --- a/tests/+did2/+unittest/testRawV1RoundTripSpike.m +++ b/tests/+did2/+unittest/testRawV1RoundTripSpike.m @@ -135,6 +135,17 @@ function testCarriesCiteTheirRows(testCase) 'neuron_extracellular.waveform_sample_times (no mean_waveform'); end +function testAHartleyWithNoSubjectIsCarriedWithANote(testCase) +% [one folder, final classes only] (Decided by Jess Haley, 2026-10-29, did-schema +% V_eta_go_forward_class_audit.md): a hartley_calc naming no subject used to +% pass through silently (jCalculation hands it back unchanged); it is now +% carried whole as v1 with a `carried_as_v1` note ([passthrough stays v1]) +v1 = hartley(); +v1.depends_on(1).value = ''; +verifyCarriedFor(testCase, {v1, presentationOf(hartley())}, ... + 'no element_id or subject_id, so no calculation subject'); +end + function testAnotherJrclustNameCarries(testCase) % [jrclust default name is notation] (Decided by Jess Haley, 2026-10-27): only % the writer's default 'jrclust.prm' is notation; any other name on the diff --git a/tests/+did2/+unittest/testTemplateLiteralTypeTraps.m b/tests/+did2/+unittest/testTemplateLiteralTypeTraps.m index 490dd271..0bec6e42 100644 --- a/tests/+did2/+unittest/testTemplateLiteralTypeTraps.m +++ b/tests/+did2/+unittest/testTemplateLiteralTypeTraps.m @@ -69,7 +69,9 @@ % --------------------------------------------------------------------- % NOTHING HERE ASSERTS A DISPOSITION % --------------------------------------------------------------------- -% Both classes stay PASSTHROUGHS. `stimulus_parameter` is held for the +% Both classes stay PASSTHROUGHS (a `stimulus_parameter` is carried whole as +% v1 since 2026-10-29, [one folder, final classes only], so its placeholder is +% kept as written). `stimulus_parameter` is held for the % stimulus model (#31) and `image_stack` / `image_stack_parameters` were taken % BACK OUT of _DELETE_PHASE8 precisely so a passthrough has a schema to % validate against. These tests gate the normalisation, not a fold. Since @@ -183,27 +185,32 @@ % ===================== stimulus_parameter.value ============================ -function testStimulusParameterEmptyValuePlaceholderIsDropped(testCase) -% THE REPAIR. The template's `value: ""` is a char in a field NDI's own schema -% types `double`, so validateTypeShape (cache.m:1229 -> :1383) rejects it. There -% is no numeric substitute that invents nothing -- 0 is a recorded measurement, -% NaN is a value the field may reject -- and absence is what "unset" means and -% the only spelling that passes (cache.m:1212-1225). +function testStimulusParameterEmptyValuePlaceholderIsCarriedAsWritten(testCase) +% INVERTED 2026-10-29 from testStimulusParameterEmptyValuePlaceholderIsDropped. +% Decided by Jess Haley, 2026-10-29 [one folder, final classes only] (did-schema +% V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no +% v1 document passes through under it, so a stimulus_parameter is now carried +% whole as v1 ([passthrough stays v1]). A carried document is not read against +% V2, so the template's `value: ""` (a char in a field NDI's schema types +% `double`, which validateTypeShape rejected) no longer has to be dropped to +% get through: it is kept as v1 wrote it, never coerced. out = runJ(stimulusParameterBody('')); verifyEmpty(testCase, out.quarantine); d = onlyClass(testCase, out, 'stimulus_parameter'); +verifyEqual(testCase, char(d.get('document_class.schema_version')), 'did_v1'); b = d.get('stimulus_parameter'); -verifyFalse(testCase, isfield(b, 'value'), ... - 'the empty char placeholder must be DROPPED, not coerced'); +verifyTrue(testCase, isfield(b, 'value'), 'the placeholder is kept, not dropped'); +verifyEqual(testCase, b.value, '', 'and not coerced'); % the two string fields are untouched: `string` accepts '' (cache.m:1349-1371) verifyEqual(testCase, b.ontology_name, 'NDIC:12'); verifyEqual(testCase, b.name, 'Command temperature constant'); end function testStimulusParameterPlaceholderDocumentValidates(testCase) -% The assertion that matters: with the placeholder dropped the document -- which -% is otherwise exactly what ndi.document('stimulus_parameter') produces -- gets -% through the validator instead of quarantining on typeMismatch. +% The assertion that matters: the document -- exactly what +% ndi.document('stimulus_parameter') produces -- gets through the validator +% instead of quarantining on typeMismatch. Since 2026-10-29 it is carried whole +% as v1 ([one folder, final classes only]), which is not validated against V2. out = runJValidated(stimulusParameterBody('')); verifyEmpty(testCase, out.quarantine); verifyEqual(testCase, numel(out.migrated), 1); diff --git a/tests/+did2/+unittest/testWholeBatchRoundTrip.m b/tests/+did2/+unittest/testWholeBatchRoundTrip.m index 367681d2..bf345d7a 100644 --- a/tests/+did2/+unittest/testWholeBatchRoundTrip.m +++ b/tests/+did2/+unittest/testWholeBatchRoundTrip.m @@ -169,10 +169,16 @@ function testAnIngestedManifestRoundTripsThroughItsDecomposition(testCase) verifyFalse(testCase, isfield(r.failures_by_class, 'epochfiles_ingested'), describe(r)); end -function testAnOpenmindsSubjectRoundTrips(testCase) +function testAnOpenmindsSubjectRoundTripsWithTheSpeciesTextAccepted(testCase) +% INVERTED 2026-10-29 from testAnOpenmindsSubjectRoundTrips, which asserted the +% species' definition and synonym were LOST. Decided by Jess Haley, 2026-10-29 +% [species text is the openMINDS equivalent] (did-schema +% V_eta_go_forward_class_audit.md): they are not stored in V2 (the registry +% records the openMINDS species instance as the term's equivalent), and "the +% round trip reports the v1 text as an accepted difference". % JH's shapes (ndi.database.fun.openMINDSobj2ndi_document): a sex states no % definition and round trips clean; a species states openMINDS's definition and -% synonym, which nothing in DID holds, and the round trip reports exactly those +% synonym, and the round trip reports exactly those as accepted (equivalent) sex = openmindsV1('wb_om_sex', 'openminds_subject', 'subject_id', 'BiologicalSex', ... 'hermaphrodite', 'PATO:0001340', '', ''); species = openmindsV1('wb_om_sp', 'openminds_subject', 'subject_id', 'Species', ... @@ -184,10 +190,12 @@ function testAnOpenmindsSubjectRoundTrips(testCase) did2.unittest.helpers.verifyRoundTrip(testCase, r, 'openminds_subject (sex)', {}); out = did2.convert.v1_to_v2({species}, 'Validate', false, 'TargetVersion', 'V_eta'); r = did2.convert.roundtrip.check(species, out.migrated); -verifyEqual(testCase, sort({r.lost.path}), ... - {'openminds.fields.definition', 'openminds.fields.synonym'}); -verifyEmpty(testCase, r.invented); -verifyEmpty(testCase, r.unplaced); +verifyFalse(testCase, r.carried); +did2.unittest.helpers.verifyRoundTrip(testCase, r, 'openminds_subject (species)', {}); +verifyTrue(testCase, all(ismember({'openminds.fields.definition', ... + 'openminds.fields.synonym'}, r.equivalent)), ... + 'the species text is reported as an accepted difference'); +verifyTrue(testCase, r.ok); end function testAnOpenmindsSubjectIdIsReadBackThroughTheEquivalentList(testCase) @@ -213,20 +221,46 @@ function testAnOpenmindsSubjectIdIsReadBackThroughTheEquivalentList(testCase) did2.unittest.helpers.verifyRoundTrip(testCase, r, 'openminds_subject (no id)', {}); end -function testAnOpenmindsElementReportsWhatItDoesNotKeep(testCase) -% Dab's shape: a cell type with an openMINDS id and a description; the -% migrator keeps neither, and the round trip says so +function testAnOpenmindsElementWithAnIdAndADescriptionIsCarried(testCase) +% INVERTED 2026-10-29 from testAnOpenmindsElementReportsWhatItDoesNotKeep, +% which asserted the element was CONVERTED and its openminds_id and description +% LOST. Decided by Jess Haley, 2026-10-29 [openMINDS element leftovers carried] +% (did-schema V_eta_go_forward_class_audit.md): "a v1 `openminds_element` +% holding an `openminds_id` or a `fields.description` is carried whole as v1" +% (carry-128), so the round trip compares the carry and nothing is lost. +% Dab's shape: a cell type with an openMINDS id and a description ct = openmindsV1('wb_om_ct', 'openminds_element', 'element_id', 'CellType', ... 'type III BNST neuron', 'EMPTY:0000073', '', ''); ct.openminds.openminds_id = 'CellType/dc2f5dd6-5cea-477c-a035-ca2dd3e6649d'; ct.openminds.fields.description = 'A type of neuron.'; out = did2.convert.v1_to_v2({ct}, 'Validate', false, 'TargetVersion', 'V_eta'); r = did2.convert.roundtrip.check(ct, out.migrated); +verifyTrue(testCase, r.carried); +did2.unittest.helpers.verifyRoundTrip(testCase, r, 'openminds_element (carried)', {}); +% each half alone carries too +for half = {'id', 'description'} + one = ct; + if strcmp(half{1}, 'id') + one.openminds.fields.description = ''; + else + one.openminds.openminds_id = ''; + end + out = did2.convert.v1_to_v2({one}, 'Validate', false, 'TargetVersion', 'V_eta'); + r = did2.convert.roundtrip.check(one, out.migrated); + verifyTrue(testCase, r.carried, ['carried for its ' half{1} ' alone']); +end +end + +function testAnOpenmindsElementWithNeitherConverts(testCase) +% the other side of [openMINDS element leftovers carried]: a cell type stating +% neither an openminds_id nor a description still converts to the term +% assertion, and round trips clean +ct = openmindsV1('wb_om_ct2', 'openminds_element', 'element_id', 'CellType', ... + 'type III BNST neuron', 'EMPTY:0000073', '', ''); +out = did2.convert.v1_to_v2({ct}, 'Validate', false, 'TargetVersion', 'V_eta'); +r = did2.convert.roundtrip.check(ct, out.migrated); verifyFalse(testCase, r.carried); -verifyEqual(testCase, sort({r.lost.path}), ... - {'openminds.fields.description', 'openminds.openminds_id'}); -verifyEmpty(testCase, r.invented); -verifyEmpty(testCase, r.unplaced); +did2.unittest.helpers.verifyRoundTrip(testCase, r, 'openminds_element (converted)', {}); end % ===================== fixtures ========================================= From e3075e4d0a74c14432c843f4daf97a8d4ae78879 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 18:39:53 +0000 Subject: [PATCH 2/3] testWholeBatchRoundTrip: compare the stored v1 source of a carried openminds_element openminds_element is read on the renamed path, so pass 1 hands a carried element back as the renamed body (depends_on value -> document_id, renamed superclasses), recorded as a passthrough; the pipeline stores the untouched v1 source (v1_to_v2.m, "A CARRY IS THE V1 SOURCE"). The test compared the renamed body with roundtrip.check and reported those renames as lost and invented (CI on db17ce8b). It now checks the passthrough is recorded and compares what is stored, through roundtrip.wholeBatch, as the table-row test above it does. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01FxKLA8Tbf9cpvDe7fuVeKf --- .../+did2/+unittest/testWholeBatchRoundTrip.m | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/tests/+did2/+unittest/testWholeBatchRoundTrip.m b/tests/+did2/+unittest/testWholeBatchRoundTrip.m index bf345d7a..0a1dffc4 100644 --- a/tests/+did2/+unittest/testWholeBatchRoundTrip.m +++ b/tests/+did2/+unittest/testWholeBatchRoundTrip.m @@ -233,10 +233,18 @@ function testAnOpenmindsElementWithAnIdAndADescriptionIsCarried(testCase) 'type III BNST neuron', 'EMPTY:0000073', '', ''); ct.openminds.openminds_id = 'CellType/dc2f5dd6-5cea-477c-a035-ca2dd3e6649d'; ct.openminds.fields.description = 'A type of neuron.'; +% openminds_element is read on the renamed path, so pass 1 hands back the +% renamed body as a passthrough and the pipeline stores the untouched v1 source +% (v1_to_v2.m, "A CARRY IS THE V1 SOURCE"; ndi.migrate.internal. +% carryUnchangedAsV1). Compare what is stored, as the table-row test does. out = did2.convert.v1_to_v2({ct}, 'Validate', false, 'TargetVersion', 'V_eta'); -r = did2.convert.roundtrip.check(ct, out.migrated); -verifyTrue(testCase, r.carried); -did2.unittest.helpers.verifyRoundTrip(testCase, r, 'openminds_element (carried)', {}); +verifyTrue(testCase, any(strcmp({out.passthrough.id}, 'wb_om_ct')), ... + 'pass 1 records the element as carried through'); +r = did2.convert.roundtrip.wholeBatch({ct}, out); +verifyEqual(testCase, r.sources_compared, 1); +verifyEqual(testCase, r.sources_ok, 1, describe(r)); +verifyTrue(testCase, isfield(r.stored_as_v1_by_class, 'openminds_element'), ... + 'the element is stored as its v1 source'); % each half alone carries too for half = {'id', 'description'} one = ct; @@ -246,8 +254,8 @@ function testAnOpenmindsElementWithAnIdAndADescriptionIsCarried(testCase) one.openminds.openminds_id = ''; end out = did2.convert.v1_to_v2({one}, 'Validate', false, 'TargetVersion', 'V_eta'); - r = did2.convert.roundtrip.check(one, out.migrated); - verifyTrue(testCase, r.carried, ['carried for its ' half{1} ' alone']); + verifyTrue(testCase, any(strcmp({out.passthrough.id}, 'wb_om_ct')), ... + ['carried for its ' half{1} ' alone']); end end From c4c7f63f4c8d72baf9fa8ed8341627962fadd657 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 19:23:02 +0000 Subject: [PATCH 3/3] Cite carry rows 129-146 at the 19 retire-class carry sites did-schema #235 adds the register rows. Each marker now names its row and each note opens with the row's tag and kind instead of 'unplaced:'. private/jModelFit.m tags carry-136 for fitcurve, carry-137 for vmspikefit. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01FxKLA8Tbf9cpvDe7fuVeKf --- .../+convert/+migrators_j/daqmetadatareader.m | 7 ++--- .../+did2/+convert/+migrators_j/daqreader.m | 7 ++--- .../+did2/+convert/+migrators_j/daqsystem.m | 7 ++--- .../+migrators_j/electrode_offset_voltage.m | 7 ++--- .../+convert/+migrators_j/filenavigator.m | 7 ++--- .../+convert/+migrators_j/hartley_calc.m | 7 ++--- .../+convert/+migrators_j/openminds_element.m | 4 +-- .../+convert/+migrators_j/private/jModelFit.m | 10 ++++--- .../+convert/+migrators_j/probe_geometry.m | 7 ++--- .../+convert/+migrators_j/site2channelmap.m | 7 ++--- .../+migrators_j/stimulus_parameter.m | 6 ++-- .../+migrators_j/subjectmeasurement.m | 28 ++++++++----------- .../+did2/+convert/+migrators_j/syncgraph.m | 7 ++--- .../+migrators_j/vmneuralresponseresiduals.m | 7 ++--- .../+migrators_j/vmspikefilteringparameters.m | 7 ++--- .../+convert/+migrators_j/vmspikesummary.m | 7 ++--- 16 files changed, 59 insertions(+), 73 deletions(-) diff --git a/src/did/+did2/+convert/+migrators_j/daqmetadatareader.m b/src/did/+did2/+convert/+migrators_j/daqmetadatareader.m index afa0ce80..6069bd36 100644 --- a/src/did/+did2/+convert/+migrators_j/daqmetadatareader.m +++ b/src/did/+did2/+convert/+migrators_j/daqmetadatareader.m @@ -138,11 +138,10 @@ % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no % v1 document passes through under it, so this document, left as v1 on purpose, % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, - % 2026-10-24) instead of passing through silently. No carry register row - % covers this reason yet (a question for the team). - % carry-register: none (no reader class and no file parameter, so nothing to declare) + % 2026-10-24) instead of passing through silently. Register row carry-129 covers it. + % carry-register: carry-129 did2.convert.reportNote('carried_as_v1', 'daqmetadatareader', jCarryId(src), ... - 'unplaced: no reader class and no file parameter, so nothing to declare ([passthrough stays v1])'); + '[carry-129] malformed: no reader class and no file parameter, so nothing to declare ([passthrough stays v1])'); bodies = {src}; % see THE GUARD above return; end diff --git a/src/did/+did2/+convert/+migrators_j/daqreader.m b/src/did/+did2/+convert/+migrators_j/daqreader.m index 2d63d862..23e9fdfc 100644 --- a/src/did/+did2/+convert/+migrators_j/daqreader.m +++ b/src/did/+did2/+convert/+migrators_j/daqreader.m @@ -155,11 +155,10 @@ % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no % v1 document passes through under it, so this document, left as v1 on purpose, % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, - % 2026-10-24) instead of passing through silently. No carry register row - % covers this reason yet (a question for the team). - % carry-register: none (no reader class and no reader string, so nothing to declare) + % 2026-10-24) instead of passing through silently. Register row carry-130 covers it. + % carry-register: carry-130 did2.convert.reportNote('carried_as_v1', 'daqreader', jCarryId(src), ... - 'unplaced: no reader class and no reader string, so nothing to declare ([passthrough stays v1])'); + '[carry-130] malformed: no reader class and no reader string, so nothing to declare ([passthrough stays v1])'); bodies = {src}; return; end diff --git a/src/did/+did2/+convert/+migrators_j/daqsystem.m b/src/did/+did2/+convert/+migrators_j/daqsystem.m index 96c7b4ea..8ab8a93c 100644 --- a/src/did/+did2/+convert/+migrators_j/daqsystem.m +++ b/src/did/+did2/+convert/+migrators_j/daqsystem.m @@ -334,11 +334,10 @@ % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no % v1 document passes through under it, so this document, left as v1 on purpose, % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, - % 2026-10-24) instead of passing through silently. No carry register row - % covers this reason yet (a question for the team). - % carry-register: none (no daqsystem class, navigator, reader or metadata reader, so nothing to declare) + % 2026-10-24) instead of passing through silently. Register row carry-131 covers it. + % carry-register: carry-131 did2.convert.reportNote('carried_as_v1', 'daqsystem', jCarryId(src), ... - 'unplaced: no daqsystem class, navigator, reader or metadata reader, so nothing to declare ([passthrough stays v1])'); + '[carry-131] malformed: no daqsystem class, navigator, reader or metadata reader, so nothing to declare ([passthrough stays v1])'); bodies = {src}; return; end diff --git a/src/did/+did2/+convert/+migrators_j/electrode_offset_voltage.m b/src/did/+did2/+convert/+migrators_j/electrode_offset_voltage.m index 490f5861..daa0c9fd 100644 --- a/src/did/+did2/+convert/+migrators_j/electrode_offset_voltage.m +++ b/src/did/+did2/+convert/+migrators_j/electrode_offset_voltage.m @@ -125,11 +125,10 @@ % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no % v1 document passes through under it, so this document, left as v1 on purpose, % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, - % 2026-10-24) instead of passing through silently. No carry register row - % covers this reason yet (a question for the team). - % carry-register: none (electrode_offset_voltage.offset is not one finite number, so nothing to fold) + % 2026-10-24) instead of passing through silently. Register row carry-133 covers it. + % carry-register: carry-133 did2.convert.reportNote('carried_as_v1', 'electrode_offset_voltage', jCarryId(src), ... - 'unplaced: electrode_offset_voltage.offset is not one finite number, so nothing to fold ([passthrough stays v1])'); + '[carry-133] malformed: electrode_offset_voltage.offset is not one finite number, so nothing to fold ([passthrough stays v1])'); bodies = {src}; % nothing to fold -> carry unchanged (discovery fallback) return; end diff --git a/src/did/+did2/+convert/+migrators_j/filenavigator.m b/src/did/+did2/+convert/+migrators_j/filenavigator.m index 65e42580..ae26bcab 100644 --- a/src/did/+did2/+convert/+migrators_j/filenavigator.m +++ b/src/did/+did2/+convert/+migrators_j/filenavigator.m @@ -166,11 +166,10 @@ % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no % v1 document passes through under it, so this document, left as v1 on purpose, % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, - % 2026-10-24) instead of passing through silently. No carry register row - % covers this reason yet (a question for the team). - % carry-register: none (no navigator class, file patterns or epoch map, so nothing to declare) + % 2026-10-24) instead of passing through silently. Register row carry-132 covers it. + % carry-register: carry-132 did2.convert.reportNote('carried_as_v1', 'filenavigator', jCarryId(src), ... - 'unplaced: no navigator class, file patterns or epoch map, so nothing to declare ([passthrough stays v1])'); + '[carry-132] malformed: no navigator class, file patterns or epoch map, so nothing to declare ([passthrough stays v1])'); bodies = {src}; % nothing to declare -- see THE GUARD above return; end diff --git a/src/did/+did2/+convert/+migrators_j/hartley_calc.m b/src/did/+did2/+convert/+migrators_j/hartley_calc.m index d1c4670f..cb6c9274 100644 --- a/src/did/+did2/+convert/+migrators_j/hartley_calc.m +++ b/src/did/+did2/+convert/+migrators_j/hartley_calc.m @@ -402,12 +402,11 @@ % 2026-10-29, did-schema V_eta_go_forward_class_audit.md): a retire class's V2 % file is deleted once no v1 document passes through under it, so it is carried % whole as v1 with a note here ([passthrough stays v1], Jess Haley, 2026-10-24) -% instead of passing through silently. No carry register row covers this -% reason yet (a question for the team). +% instead of passing through silently. Register row carry-138 covers it. if isempty(jSubjectOf(preBody, {'element_id', 'subject_id'})) - % carry-register: none (a hartley_calc with no element_id or subject_id) + % carry-register: carry-138 did2.convert.reportNote('carried_as_v1', 'hartley_calc', sourceId(preBody), ... - 'unplaced: no element_id or subject_id, so no calculation subject ([passthrough stays v1])'); + '[carry-138] awaiting_home: no element_id or subject_id, so no calculation subject ([passthrough stays v1])'); bodies = {preBody}; return; end diff --git a/src/did/+did2/+convert/+migrators_j/openminds_element.m b/src/did/+did2/+convert/+migrators_j/openminds_element.m index 4e6c7b22..8096f9e1 100644 --- a/src/did/+did2/+convert/+migrators_j/openminds_element.m +++ b/src/did/+did2/+convert/+migrators_j/openminds_element.m @@ -42,9 +42,9 @@ % V_eta_go_forward_class_audit.md: a retire class's V2 file is deleted % once no v1 document passes through under it). No register row covers a % missing block yet (a question for the team). - % carry-register: none (an openminds_element with no openminds block) + % carry-register: carry-146 did2.convert.reportNote('carried_as_v1', 'openminds_element', sourceIdOf(preBody), ... - 'unplaced: no openminds block, so no term to assert ([passthrough stays v1])'); + '[carry-146] malformed: no openminds block, so no term to assert ([passthrough stays v1])'); v2Body = {preBody}; return; end diff --git a/src/did/+did2/+convert/+migrators_j/private/jModelFit.m b/src/did/+did2/+convert/+migrators_j/private/jModelFit.m index 3a140fde..51ec07b1 100644 --- a/src/did/+did2/+convert/+migrators_j/private/jModelFit.m +++ b/src/did/+did2/+convert/+migrators_j/private/jModelFit.m @@ -31,12 +31,14 @@ % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once % no v1 document passes through under it, so this document, refused on % purpose, is carried whole as v1 with a note ([passthrough stays v1], Jess - % Haley, 2026-10-24) instead of passing through silently. No carry register - % row covers this reason yet (a question for the team). + % Haley, 2026-10-24) instead of passing through silently. Register rows + % carry-136 (fitcurve) and carry-137 (vmspikefit) cover it. stamp = did2.convert.v1read.readStamp(preBody); - % carry-register: none (a fit with no subject: no element_id or subject_id) + tag = '[carry-136]'; + if strcmp(stamp.class_name, 'vmspikefit'); tag = '[carry-137]'; end + % carry-register: carry-136 carry-137 did2.convert.reportNote('carried_as_v1', stamp.class_name, jCarryId(preBody), ... - 'unplaced: no element_id or subject_id, so no calculation subject ([passthrough stays v1])'); + [tag ' awaiting_home: no element_id or subject_id, so no calculation subject ([passthrough stays v1])']); return; end [software, swId, execEnv] = jSoftwareFromApp(preBody, 'RequireSession', true); diff --git a/src/did/+did2/+convert/+migrators_j/probe_geometry.m b/src/did/+did2/+convert/+migrators_j/probe_geometry.m index 8b267dac..526ef8cc 100644 --- a/src/did/+did2/+convert/+migrators_j/probe_geometry.m +++ b/src/did/+did2/+convert/+migrators_j/probe_geometry.m @@ -205,11 +205,10 @@ % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no % v1 document passes through under it, so this document, left as v1 on purpose, % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, - % 2026-10-24) instead of passing through silently. No carry register row - % covers this reason yet (a question for the team). - % carry-register: none (no per-site axis values, so nothing to fold) + % 2026-10-24) instead of passing through silently. Register row carry-134 covers it. + % carry-register: carry-134 did2.convert.reportNote('carried_as_v1', 'probe_geometry', jCarryId(src), ... - 'unplaced: no per-site axis values, so nothing to fold ([passthrough stays v1])'); + '[carry-134] malformed: no per-site axis values, so nothing to fold ([passthrough stays v1])'); bodies = {src}; % nothing to fold -> carry unchanged (discovery fallback) return; end diff --git a/src/did/+did2/+convert/+migrators_j/site2channelmap.m b/src/did/+did2/+convert/+migrators_j/site2channelmap.m index cd6449e6..b7fe228c 100644 --- a/src/did/+did2/+convert/+migrators_j/site2channelmap.m +++ b/src/did/+did2/+convert/+migrators_j/site2channelmap.m @@ -80,11 +80,10 @@ % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no % v1 document passes through under it, so this document, left as v1 on purpose, % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, - % 2026-10-24) instead of passing through silently. No carry register row - % covers this reason yet (a question for the team). - % carry-register: none (site2channelmap has no V2 home (its map needs the probe_geometry join)) + % 2026-10-24) instead of passing through silently. Register row carry-139 covers it. + % carry-register: carry-139 did2.convert.reportNote('carried_as_v1', 'site2channelmap', jCarryId(preBody), ... - 'unplaced: site2channelmap has no V2 home (its map needs the probe_geometry join) ([passthrough stays v1])'); + '[carry-139] awaiting_home: site2channelmap has no V2 home (its map needs the probe_geometry join) ([passthrough stays v1])'); end bodies = {preBody}; % the carry of the untouched v1 source end diff --git a/src/did/+did2/+convert/+migrators_j/stimulus_parameter.m b/src/did/+did2/+convert/+migrators_j/stimulus_parameter.m index f24fbbbe..b4a221e6 100644 --- a/src/did/+did2/+convert/+migrators_j/stimulus_parameter.m +++ b/src/did/+did2/+convert/+migrators_j/stimulus_parameter.m @@ -165,9 +165,9 @@ % v1 document passes through under it, so this document, held for the stimulus % model (#31) and left as v1 on purpose, is carried whole as v1 with a note % ([passthrough stays v1], Jess Haley, 2026-10-24) instead of passing through -% silently. No carry register row covers it yet (a question for the team). -% carry-register: none (stimulus_parameter has no V2 home until the stimulus model) +% silently. Register row carry-140 covers it. +% carry-register: carry-140 did2.convert.reportNote('carried_as_v1', 'stimulus_parameter', jCarryId(preBody), ... - 'unplaced: stimulus_parameter has no V2 home until the stimulus model ([passthrough stays v1])'); + '[carry-140] awaiting_home: stimulus_parameter has no V2 home until the stimulus model ([passthrough stays v1])'); bodies = {preBody}; end diff --git a/src/did/+did2/+convert/+migrators_j/subjectmeasurement.m b/src/did/+did2/+convert/+migrators_j/subjectmeasurement.m index 930c7ed4..71d44678 100644 --- a/src/did/+did2/+convert/+migrators_j/subjectmeasurement.m +++ b/src/did/+did2/+convert/+migrators_j/subjectmeasurement.m @@ -154,11 +154,10 @@ % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no % v1 document passes through under it, so this document, left as v1 on purpose, % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, - % 2026-10-24) instead of passing through silently. No carry register row - % covers this reason yet (a question for the team). - % carry-register: none (no subject_id, so no observation) + % 2026-10-24) instead of passing through silently. Register row carry-144 covers it. + % carry-register: carry-144 did2.convert.reportNote('carried_as_v1', 'subjectmeasurement', jCarryId(preBody), ... - 'unplaced: no subject_id, so no observation ([passthrough stays v1])'); + '[carry-144] malformed: no subject_id, so no observation ([passthrough stays v1])'); return; end @@ -170,11 +169,10 @@ % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no % v1 document passes through under it, so this document, left as v1 on purpose, % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, - % 2026-10-24) instead of passing through silently. No carry register row - % covers this reason yet (a question for the team). - % carry-register: none (subjectmeasurement.measurement is empty, so no variable) + % 2026-10-24) instead of passing through silently. Register row carry-144 covers it. + % carry-register: carry-144 did2.convert.reportNote('carried_as_v1', 'subjectmeasurement', jCarryId(preBody), ... - 'unplaced: subjectmeasurement.measurement is empty, so no variable ([passthrough stays v1])'); + '[carry-144] malformed: subjectmeasurement.measurement is empty, so no variable ([passthrough stays v1])'); return; end @@ -186,11 +184,10 @@ % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no % v1 document passes through under it, so this document, left as v1 on purpose, % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, - % 2026-10-24) instead of passing through silently. No carry register row - % covers this reason yet (a question for the team). - % carry-register: none (subjectmeasurement.value is not one finite number) + % 2026-10-24) instead of passing through silently. Register row carry-144 covers it. + % carry-register: carry-144 did2.convert.reportNote('carried_as_v1', 'subjectmeasurement', jCarryId(preBody), ... - 'unplaced: subjectmeasurement.value is not one finite number ([passthrough stays v1])'); + '[carry-144] malformed: subjectmeasurement.value is not one finite number ([passthrough stays v1])'); return; end @@ -224,11 +221,10 @@ % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no % v1 document passes through under it, so this document, left as v1 on purpose, % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, - % 2026-10-24) instead of passing through silently. No carry register row - % covers this reason yet (a question for the team). - % carry-register: none (no quantity kind is named by subjectmeasurement.measurement) + % 2026-10-24) instead of passing through silently. Register row carry-145 covers it. + % carry-register: carry-145 did2.convert.reportNote('carried_as_v1', 'subjectmeasurement', jCarryId(preBody), ... - 'unplaced: no quantity kind is named by subjectmeasurement.measurement ([passthrough stays v1])'); + '[carry-145] awaiting_home: no quantity kind is named by subjectmeasurement.measurement ([passthrough stays v1])'); end return; end diff --git a/src/did/+did2/+convert/+migrators_j/syncgraph.m b/src/did/+did2/+convert/+migrators_j/syncgraph.m index 4467b1d1..b43642c5 100644 --- a/src/did/+did2/+convert/+migrators_j/syncgraph.m +++ b/src/did/+did2/+convert/+migrators_j/syncgraph.m @@ -171,11 +171,10 @@ % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no % v1 document passes through under it, so this document, left as v1 on purpose, % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, - % 2026-10-24) instead of passing through silently. No carry register row - % covers this reason yet (a question for the team). - % carry-register: none (no session id, so no policy can be anchored) + % 2026-10-24) instead of passing through silently. Register row carry-135 covers it. + % carry-register: carry-135 did2.convert.reportNote('carried_as_v1', 'syncgraph', jCarryId(src), ... - 'unplaced: no session id, so no policy can be anchored ([passthrough stays v1])'); + '[carry-135] malformed: no session id, so no policy can be anchored ([passthrough stays v1])'); bodies = {src}; % THE GUARD -- see the header return; end diff --git a/src/did/+did2/+convert/+migrators_j/vmneuralresponseresiduals.m b/src/did/+did2/+convert/+migrators_j/vmneuralresponseresiduals.m index bcef485a..1c46cdf1 100644 --- a/src/did/+did2/+convert/+migrators_j/vmneuralresponseresiduals.m +++ b/src/did/+did2/+convert/+migrators_j/vmneuralresponseresiduals.m @@ -97,11 +97,10 @@ % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no % v1 document passes through under it, so this document, left as v1 on purpose, % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, - % 2026-10-24) instead of passing through silently. No carry register row - % covers this reason yet (a question for the team). - % carry-register: none (vmneuralresponseresiduals has no V2 home) + % 2026-10-24) instead of passing through silently. Register row carry-141 covers it. + % carry-register: carry-141 did2.convert.reportNote('carried_as_v1', 'vmneuralresponseresiduals', jCarryId(preBody), ... - 'unplaced: vmneuralresponseresiduals has no V2 home ([passthrough stays v1])'); + '[carry-141] awaiting_home: vmneuralresponseresiduals has no V2 home ([passthrough stays v1])'); end bodies = {preBody}; % the carry of the untouched v1 source end diff --git a/src/did/+did2/+convert/+migrators_j/vmspikefilteringparameters.m b/src/did/+did2/+convert/+migrators_j/vmspikefilteringparameters.m index b308fab9..eb9924ea 100644 --- a/src/did/+did2/+convert/+migrators_j/vmspikefilteringparameters.m +++ b/src/did/+did2/+convert/+migrators_j/vmspikefilteringparameters.m @@ -165,11 +165,10 @@ % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no % v1 document passes through under it, so this document, left as v1 on purpose, % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, - % 2026-10-24) instead of passing through silently. No carry register row - % covers this reason yet (a question for the team). - % carry-register: none (vmspikefilteringparameters.spiketimes holds a result, which the method parameters do not keep) + % 2026-10-24) instead of passing through silently. Register row carry-143 covers it. + % carry-register: carry-143 did2.convert.reportNote('carried_as_v1', 'vmspikefilteringparameters', jCarryId(preBody), ... - 'unplaced: vmspikefilteringparameters.spiketimes holds a result, which the method parameters do not keep ([passthrough stays v1])'); + '[carry-143] awaiting_home: vmspikefilteringparameters.spiketimes holds a result, which the method parameters do not keep ([passthrough stays v1])'); bodies = {preBody}; return; end diff --git a/src/did/+did2/+convert/+migrators_j/vmspikesummary.m b/src/did/+did2/+convert/+migrators_j/vmspikesummary.m index 9884307e..d490e66f 100644 --- a/src/did/+did2/+convert/+migrators_j/vmspikesummary.m +++ b/src/did/+did2/+convert/+migrators_j/vmspikesummary.m @@ -108,11 +108,10 @@ % V_eta_go_forward_class_audit.md): a retire class's V2 file is deleted once no % v1 document passes through under it, so this document, left as v1 on purpose, % is carried whole as v1 with a note ([passthrough stays v1], Jess Haley, - % 2026-10-24) instead of passing through silently. No carry register row - % covers this reason yet (a question for the team). - % carry-register: none (vmspikesummary has no V2 home) + % 2026-10-24) instead of passing through silently. Register row carry-142 covers it. + % carry-register: carry-142 did2.convert.reportNote('carried_as_v1', 'vmspikesummary', jCarryId(preBody), ... - 'unplaced: vmspikesummary has no V2 home ([passthrough stays v1])'); + '[carry-142] awaiting_home: vmspikesummary has no V2 home ([passthrough stays v1])'); end bodies = {preBody}; % the carry of the untouched v1 source end