From d5b8fbc3e6e86a96cc8137e71ad99044780bf9a6 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 21 May 2026 21:37:17 +0000 Subject: [PATCH] did: fix CI failures from ndi-matlab#802 Three remaining V_alpha-side gaps surfaced by the symmetry tests on ndi-matlab#802: 1. did.implementations.doc2sql line 52 read `dependsOn.value` directly when assembling the SQL meta-data string. Missed in the original sweep. Migrated to use did.document.i_readDependencyTarget so V_delta bodies (with `document_id`) flow through cleanly. 2. did.database/validate_doc_vs_schema line 1348 (the per-property-block field-set assertion) compared the V_alpha-shaped schema's declared field names against the actual body field names with a strict strjoin equality. After V_delta renames the schema declaration "ndi_daqmetadatareader_class" no longer matches the body's "reader_class". Re-compare via a new normalisation pass on mismatch: both sides go through did.document.i_normalizePropertyBlockFields, which maps V_delta canonical names back to V_alpha legacy names per the small rename table. Only daqmetadatareader.reader_class is in the table today; extend as additional block-level renames surface. The subfield-level renames (probe_location.location.node etc.) are not direct property-block fields and don't hit this code path. 3. did.document/i_normalizeDependsOn early-returned on empty depends_on arrays, leaving the schema as whatever it was instead of canonicalising to {name, document_id}. Subsequent set_dependency_value appends would then attempt `struct('name', ..., 'document_id', ...)` against a `struct('name', {}, 'id', {})` schema and trip heterogeneousStrucAssignment. Now rebuilds the empty array with the canonical schema. Matches the same fix on ndi.compat.normalizeDependsOn (separate PR #802). New helper: - did.document.i_normalizePropertyBlockFields(blockName, names) - static-hidden. Normalises a list of field names within a class property block by remapping V_delta canonical -> V_alpha legacy via a small embedded rename table. Used by the V_alpha field-set validator. --- src/did/+did/+implementations/doc2sql.m | 4 ++- src/did/+did/database.m | 15 +++++++++ src/did/+did/document.m | 44 +++++++++++++++++++++++-- 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/did/+did/+implementations/doc2sql.m b/src/did/+did/+implementations/doc2sql.m index 7f232bc..5117b64 100644 --- a/src/did/+did/+implementations/doc2sql.m +++ b/src/did/+did/+implementations/doc2sql.m @@ -49,7 +49,9 @@ dependsOn = getField(doc_props, 'depends_on'); if isstruct(dependsOn) - allData = [{dependsOn.name}; {dependsOn.value}]; + targets = arrayfun(@(e) did.document.i_readDependencyTarget(e), ... + dependsOn, 'UniformOutput', false); + allData = [{dependsOn.name}; targets]; dependsOn = sprintf('%s,%s;',allData{:}); end sqlMetaData.columns(end+1) = newColumn('depends_on', dependsOn); diff --git a/src/did/+did/database.m b/src/did/+did/database.m index 45cdfc3..33ee86c 100644 --- a/src/did/+did/database.m +++ b/src/did/+did/database.m @@ -1345,6 +1345,21 @@ function validate_doc_vs_schema(database_obj, docProps, schemaStruct, all_ids) expectedSubFields = strjoin(unique({expected.name}),','); docSubFields = strjoin(unique(fieldnames(docValue)),','); areSame = strcmpi(expectedSubFields,docSubFields); + if ~areSame + % Compensate for V_alpha->V_delta property-block + % field renames the V_alpha validator does not + % know about. Normalise both sides via the + % small alias map and re-compare. See #801. + normalizedExpected = ... + did.document.i_normalizePropertyBlockFields( ... + field, {expected.name}); + normalizedDoc = ... + did.document.i_normalizePropertyBlockFields( ... + field, fieldnames(docValue)); + areSame = isequal( ... + sort(normalizedExpected(:)), ... + sort(normalizedDoc(:))); + end assert(areSame,'DID:Database:ValidationFields', ... 'Dissimilar sub-fields defined/found for %s field in %s (expected fields "%s" <=> actual fields "%s")', ... field, doc_name, expectedSubFields, docSubFields); diff --git a/src/did/+did/document.m b/src/did/+did/document.m index a115574..673f85a 100644 --- a/src/did/+did/document.m +++ b/src/did/+did/document.m @@ -686,6 +686,40 @@ end end % i_readDependencyTarget + function names = i_normalizePropertyBlockFields(blockName, names) + % i_normalizePropertyBlockFields - normalise a list of + % field names inside a class property block so V_delta + % renames compare equal to their V_alpha originals. + % + % Used by did.database/validate_doc_vs_schema when the + % schema (V_alpha-shaped) declares field names that + % V_delta migrators have since renamed. Rather than + % maintain two copies of every schema, normalise both + % sides via this map and compare. + % + % Currently covers only `daqmetadatareader.reader_class` + % (V_delta) -> `ndi_daqmetadatareader_class` (V_alpha). + % Extend the rename map below as additional block-level + % renames surface. The probe_location / treatment / + % ontology_image / ontology_label rows are + % subfield-level (location.node, treatment_name.node, ...) + % and do not appear as direct property-block fields, so + % they're not in this table. + arguments + blockName (1,:) char + names cell + end + renames = containers.Map( ... + {'daqmetadatareader.reader_class'}, ... + {'ndi_daqmetadatareader_class'}); + for k = 1:numel(names) + key = [blockName '.' names{k}]; + if isKey(renames, key) + names{k} = renames(key); + end + end + end % i_normalizePropertyBlockFields + function body = i_normalizeDependsOn(body) % i_normalizeDependsOn - canonicalise depends_on entry % keys to V_delta `document_id`. Accepts `id` (V_alpha) @@ -696,8 +730,7 @@ % Called from the constructor so every dependency % accessor on this class can rely on the invariant % "after construction, depends_on uses document_id". - if ~isstruct(body) || ~isfield(body, 'depends_on') ... - || isempty(body.depends_on) + if ~isstruct(body) || ~isfield(body, 'depends_on') return; end deps = body.depends_on; @@ -707,6 +740,13 @@ hasId = isfield(deps, 'id'); hasValue = isfield(deps, 'value'); hasDocId = isfield(deps, 'document_id'); + if numel(deps) == 0 + % Empty struct array: rebuild with canonical + % {name, document_id} schema so subsequent + % set_dependency_value appends are uniform. + body.depends_on = struct('name', {}, 'document_id', {}); + return; + end if ~hasId && ~hasValue if hasDocId return;