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
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,14 @@
bodies = preBody;
return;
end
bodies = [{preBody}, refs];
% the document is kept beside its clock references, so it is emitted
% complete: the superclass list of its class, from the schema
% (did2.build.superclasses; [early migration pieces fixed] (2), Jess Haley,
% 2026-10-26: "the builder produces a complete document"; before,
% v1_to_v2's ensureClassBlocks rewrote v1's list)
kept = preBody;
kept.document_class.superclasses = did2.build.superclasses('daqreader_epochdata_ingested');
bodies = [{kept}, refs];
end

% ===================== small helpers =======================================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,14 @@
% document rather than being deleted -- deciding it is redundant is the fold's
% call to make, not this one's.

bodies = [{preBody}, refs];
% the document is kept beside its clock references, so it is emitted
% complete: the superclass list of its class, from the schema
% (did2.build.superclasses; [early migration pieces fixed] (2), Jess Haley,
% 2026-10-26: "the builder produces a complete document"; before,
% v1_to_v2's ensureClassBlocks rewrote v1's list)
kept = preBody;
kept.document_class.superclasses = did2.build.superclasses('daqreader_image_epochdata_ingested');
bodies = [{kept}, refs];
end

% ===================== small helpers =======================================
Expand Down
4 changes: 4 additions & 0 deletions src/did/+did2/+convert/+migrators_j/private/jDemoFold.m
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@
% ---- the rename, and the consumed blocks ----
v2Body = preBody;
v2Body.document_class.class_name = 'demo';
% the superclass list of `demo`, from the schema (did2.build.superclasses), so
% the document is complete as emitted ([early migration pieces fixed] (2),
% Jess Haley, 2026-10-26; before, v1_to_v2's ensureClassBlocks rewrote it)
v2Body.document_class.superclasses = did2.build.superclasses('demo');
v2Body.demo = demoBlock;
% `demo_ndi_mock` is the concrete block of the mock class and carries no fields;
% it is listed so the removal does not depend on ensureClassBlocks' empty-block
Expand Down
51 changes: 51 additions & 0 deletions src/did/+did2/+convert/handOnSuperclasses.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
function body = handOnSuperclasses(body, schemaCacheOverride)
%HANDONSUPERCLASSES Write V2's superclass list on a document pass 1 hands on.
%
% BODY = did2.convert.handOnSuperclasses(BODY, SCHEMACACHE) (SCHEMACACHE []
% for the shared cache). The one step on pass 1's hand-on path: a v1
% document whose migrator hands it on unchanged to a later batch pass
% (did2.convert.v1_to_v2, HAND_ON_CLASSES: syncrule, epochfiles_ingested,
% stimulus_response_scalar, stimulus_response_scalar_parameters_basic,
% openminds) keeps every field as v1 wrote it; only its
% document_class.superclasses is rewritten from v1's form ({definition}
% paths) into V2's ({class_name, class_version}, did2.build.superclasses),
% and an EMPTY property block v1 wrote for a parent class that V2 hosts no
% block for (an abstract parent, e.g. `stimulus_response_scalar_parameters`)
% is dropped. A NON-empty such block is left for validation to report.
%
% Decided by Jess Haley, 2026-11-02 [handed-on classes carried at the end]
% (2) (did-schema docs/plans/did_v2_go_forward_class_audit.md;
% docs/V2_PLAN.md G98 (a)): "a small named step on the hand-on path writes
% V2's superclass list for the five classes pass 1 hands on unchanged ...
% and `ensureClassBlocks` is deleted". This is that step; it replaces what
% the padding did for these five (DID-matlab #364 measured: the superclass
% list, and one empty v1 parent block).
%
% Errors when the schema does not know the class (did2.schema.cache), so a
% hand-on class that lost its schema is a quarantine, not a silent pass.
%
% See also did2.build.superclasses, did2.convert.v1_to_v2.

arguments
body (1,1) struct
schemaCacheOverride = []
end

cache = schemaCacheOverride;
if isempty(cache)
cache = did2.schema.cache.shared();
end
className = char(body.document_class.class_name);
body.document_class.superclasses = did2.build.superclasses(className, ...
'SchemaCache', cache);

hosted = cache.resolvePlacement(className).blocksContributed;
chain = [reshape(cache.superclasses(className), 1, []), {className}];
for cls = setdiff(chain, hosted)
name = cls{1};
if isfield(body, name) && isstruct(body.(name)) ...
&& (isempty(body.(name)) || isempty(fieldnames(body.(name))))
body = rmfield(body, name);
end
end
end
7 changes: 6 additions & 1 deletion src/did/+did2/+convert/resolveValidIntervals.m
Original file line number Diff line number Diff line change
Expand Up @@ -996,9 +996,14 @@
if ~isempty(anchor.tolerance_seconds)
% Written ONLY when the source clock stated a tolerance. An empty tolerance
% cell would assert "the timeline is good to 0 s", which the source never
% said; with no block, ensureClassBlocks pads it.
% said; with none, the block is written empty here, so the document is
% complete as built ([early migration pieces fixed] (2), Jess Haley,
% 2026-10-26: "the builder produces a complete document"; before,
% ensureClassBlocks added it).
ref.time_reference = struct('clock_tolerance', ...
durationCell(anchor.tolerance_seconds));
else
ref.time_reference = struct();
end
value = struct('clock', struct('node', '', 'name', anchor.clock), ...
'start', durationCell(startSeconds));
Expand Down
32 changes: 30 additions & 2 deletions src/did/+did2/+convert/v1_to_v2.m
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,21 @@
% 2026-11-01 [last rename-pass classes]).
PARENT_ONLY_CLASSES = {'app', 'calculator', 'epochid', 'stimulus_response', ...
'stimulus_response_scalar_parameters'};
% v1 classes whose migrator HANDS ON a document unchanged to a later batch
% pass (resolveClockAlignment: syncrule; epochMint / resolveEpochProbemap:
% epochfiles_ingested; resolveResponseParameters: stimulus_response_scalar and
% its parameters; resolveOpenmindsCitations: openminds). Such a document gets
% V2's superclass list from its one named step,
% did2.convert.handOnSuperclasses (Decided by Jess Haley, 2026-11-02
% [handed-on classes carried at the end] (2), which names those five, the
% ones the corpora hold). daqreader_epochdata_ingested and
% daqreader_image_epochdata_ingested are handed on the same way (to epochMint)
% when their source names no epoch document; no corpus holds one, and the
% padding probe on the unit fixtures found them (DID-matlab
% claude/W-padding-probe), so they take the same step.
HAND_ON_CLASSES = {'syncrule', 'epochfiles_ingested', 'stimulus_response_scalar', ...
'stimulus_response_scalar_parameters_basic', 'openminds', ...
'daqreader_epochdata_ingested', 'daqreader_image_epochdata_ingested'};

% a migrator transcribes the v1 source's own terms, which the term
% registry (strictMode 'TermRegistry', tenet T19) need not hold yet: the check
Expand Down Expand Up @@ -301,6 +316,8 @@
% yet produce a complete document. Denominator: the converted bodies padded.
paddedInspected = 0;
paddedNames = {};
handOnNames = {};
handOnValues = [];
paddedValues = [];
% DIMENSION TERMS WITH NO NODE (Jess Haley 2026-10-22 [covariate details],
% answer "B" to 5.4): a dimension's term is bound `preferred` for now, and
Expand Down Expand Up @@ -709,9 +726,18 @@
outBody.document_class.schema_version = 'did_v1';
end
else
outBody = did2.convert.ensureClassBlocks(v2Bodies{bi}, options.SchemaCache);
outBody = v2Bodies{bi};
if passedThrough && any(strcmp(className, HAND_ON_CLASSES))
% the hand-on step ([handed-on classes carried at the
% end] (2)): V2's superclass list, nothing else
outBody = did2.convert.handOnSuperclasses(outBody, options.SchemaCache);
[handOnNames, handOnValues] = bumpClassCounter( ...
handOnNames, handOnValues, className);
end
beforePadding = outBody;
outBody = did2.convert.ensureClassBlocks(outBody, options.SchemaCache);
paddedInspected = paddedInspected + 1;
if ~isequaln(outBody, v2Bodies{bi})
if ~isequaln(outBody, beforePadding)
[paddedNames, paddedValues] = bumpClassCounter( ...
paddedNames, paddedValues, className);
end
Expand Down Expand Up @@ -925,6 +951,8 @@
'carried_failing_veta_validation_by_class', buildByClassTable(carriedUnvalNames, carriedUnvalValues), ...
'legacy_ndi_document', legacy, ...
'carried_v1_template_check', carriedTemplateCheck, ...
'hand_on_superclasses', struct('bodies_written', sum(handOnValues), ...
'by_class', buildByClassTable(handOnNames, handOnValues)), ...
'class_blocks_padding', struct('bodies_inspected', paddedInspected, ...
'bodies_changed', sum(paddedValues), ...
'changed_by_class', buildByClassTable(paddedNames, paddedValues)), ...
Expand Down
Loading