Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/did/+did2/+convert/+migrators_j/Contents.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions src/did/+did2/+convert/+migrators_j/daqmetadatareader.m
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,14 @@
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. Register row carry-129 covers it.
% carry-register: carry-129
did2.convert.reportNote('carried_as_v1', 'daqmetadatareader', jCarryId(src), ...
'[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
Expand Down
8 changes: 8 additions & 0 deletions src/did/+did2/+convert/+migrators_j/daqreader.m
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,14 @@
% 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. Register row carry-130 covers it.
% carry-register: carry-130
did2.convert.reportNote('carried_as_v1', 'daqreader', jCarryId(src), ...
'[carry-130] malformed: no reader class and no reader string, so nothing to declare ([passthrough stays v1])');
bodies = {src};
return;
end
Expand Down
15 changes: 14 additions & 1 deletion src/did/+did2/+convert/+migrators_j/daqsystem.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -325,6 +326,18 @@
% 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. Register row carry-131 covers it.
% carry-register: carry-131
did2.convert.reportNote('carried_as_v1', 'daqsystem', jCarryId(src), ...
'[carry-131] malformed: no daqsystem class, navigator, reader or metadata reader, so nothing to declare ([passthrough stays v1])');
bodies = {src};
return;
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,14 @@

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. Register row carry-133 covers it.
% carry-register: carry-133
did2.convert.reportNote('carried_as_v1', 'electrode_offset_voltage', jCarryId(src), ...
'[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
Expand Down
8 changes: 8 additions & 0 deletions src/did/+did2/+convert/+migrators_j/filenavigator.m
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,14 @@
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. Register row carry-132 covers it.
% carry-register: carry-132
did2.convert.reportNote('carried_as_v1', 'filenavigator', jCarryId(src), ...
'[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
Expand Down
13 changes: 13 additions & 0 deletions src/did/+did2/+convert/+migrators_j/hartley_calc.m
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,19 @@
% 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. Register row carry-138 covers it.
if isempty(jSubjectOf(preBody, {'element_id', 'subject_id'}))
% carry-register: carry-138
did2.convert.reportNote('carried_as_v1', 'hartley_calc', sourceId(preBody), ...
'[carry-138] awaiting_home: 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, ...
Expand Down
43 changes: 41 additions & 2 deletions src/did/+did2/+convert/+migrators_j/openminds_element.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -33,14 +35,44 @@
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: carry-146
did2.convert.reportNote('carried_as_v1', 'openminds_element', sourceIdOf(preBody), ...
'[carry-146] malformed: no openminds block, so no term to assert ([passthrough stays v1])');
v2Body = {preBody};
return;
end

variable = variableForType(jGetChar(block, 'openminds_type'));

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'), ...
Expand Down Expand Up @@ -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)
Expand Down
15 changes: 15 additions & 0 deletions src/did/+did2/+convert/+migrators_j/private/jCarryId.m
Original file line number Diff line number Diff line change
@@ -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
18 changes: 16 additions & 2 deletions src/did/+did2/+convert/+migrators_j/private/jModelFit.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,29 @@
% 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.

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. Register rows
% carry-136 (fitcurve) and carry-137 (vmspikefit) cover it.
stamp = did2.convert.v1read.readStamp(preBody);
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), ...
[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);
Expand Down
8 changes: 8 additions & 0 deletions src/did/+did2/+convert/+migrators_j/probe_geometry.m
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,14 @@
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. Register row carry-134 covers it.
% carry-register: carry-134
did2.convert.reportNote('carried_as_v1', 'probe_geometry', jCarryId(src), ...
'[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
Expand Down
13 changes: 11 additions & 2 deletions src/did/+did2/+convert/+migrators_j/site2channelmap.m
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,17 @@
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. Register row carry-139 covers it.
% carry-register: carry-139
did2.convert.reportNote('carried_as_v1', 'site2channelmap', jCarryId(preBody), ...
'[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

% ===================== small helpers =======================================
Expand Down
24 changes: 19 additions & 5 deletions src/did/+did2/+convert/+migrators_j/stimulus_parameter.m
Original file line number Diff line number Diff line change
@@ -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'.
%
Expand Down Expand Up @@ -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'' ' ...
Expand All @@ -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. Register row carry-140 covers it.
% carry-register: carry-140
did2.convert.reportNote('carried_as_v1', 'stimulus_parameter', jCarryId(preBody), ...
'[carry-140] awaiting_home: stimulus_parameter has no V2 home until the stimulus model ([passthrough stays v1])');
bodies = {preBody};
end
Loading
Loading