Repository navigation
File-series declaration validation and the add_file series-member guard were not ported (DID-matlab c80ba33) #69
Description
Found while writing the tests for #68. Two guards that DID-matlab added in the same commit the Python file-series work claims to port — c80ba33, "Add file series to did.document: declaration, authoring, enumeration" — have no Python counterpart, and the bridge does not record their absence.
1. Document.__init__ does not validate file declarations
MATLAB's did.document constructor calls localValidateFileDeclarations (+did/document.m:67, body at :1524), which refuses four malformed declarations:
| MATLAB identifier | Refuses |
|---|---|
DID:Document:fileDeclarations:duplicateSeries |
the same series name twice (case-insensitively) |
DID:Document:fileDeclarations:seriesNotInFileList |
a series whose name is not also in file_list — the name is its manifest, an ordinary file |
DID:Document:fileDeclarations:seriesAndNumberedEntry |
NAME a series while NAME_# is in file_list — NAME_12 would match both mechanisms |
DID:Document:fileDeclarations:shadowedBySeries |
a literal NAME_3 in file_list when NAME is a series — the series answers for it first, so the entry is unreachable |
MATLAB's comment is explicit that this is not hygiene: "it is what makes the two mechanisms safe to coexist."
Python raises none of them:
from did.document import Document
from did.ido import IDO
props = {
"base": {"id": IDO.unique_id(), "name": "test"},
"files": {"file_list": ["plainfile.ext"], "file_series": ["chunkdata.bin"]},
}
Document(props).series_names() # ['chunkdata.bin'] -- acceptedAll four cases construct without complaint. A chunkdata.bin series with no chunkdata.bin in file_list has nowhere to put its manifest: add_file_series writes the manifest and calls add_file(name, ...), which appends a file_info entry regardless, so the document ends up with a manifest that is_in_file_list was never meant to serve.
2. Document.add_file does not refuse a series-member name
MATLAB (+did/document.m:496):
error('DID:Document:add_file:isSeriesMember', ...
['"%s" is a member of the file series "%s". Members are ' ...
'added together by addFileSeries, which records them in ' ...
'the series manifest.'], name, seriesStem);Python accepts it:
d = Document("demoSeries") # once demoSeries.json is ported; see #68
d.add_file("chunkdata.bin_5", "/tmp/a")
# -> file_info gains a 'chunkdata.bin_5' entry, no errorThat is the divergence the guard exists to prevent. A member with its own file_info entry is invisible to the manifest, so series_members and series_count do not know about it while file_uids does — the two mechanisms then disagree about what the series contains, and which one answers depends on which code path a caller took.
Why the bridge did not catch this
+did/document.m's entry records matlab_last_sync_hash: 78583f6 with status: ported. c80ba33 is an ancestor of 78583f6, so the drift check is green: the bridge asserts this commit was reviewed. Nothing in the entry's decision_log mentions either guard, as a deviation or otherwise.
Related but separate, and noted here only so it is written down somewhere: the is_in_file_list member entry's decision_log says "Exact match. Synchronized 2026-03-16." Python's is_in_file_list is a file_info-only lookup returning (bool, info_dict, index); MATLAB's searches file_list, resolves NAME_12 to a NAME_# entry, falls back to series membership, and returns (b, msg, fI_index). The Python docstring calls the narrowing deliberate and gives a reason; the bridge entry claims the opposite. Whichever is right, they should not disagree — and the series fallback MATLAB added in c80ba33 is missing from the Python function either way.
Suggested fix
Port localValidateFileDeclarations as a private module function called from Document.__init__, and the member check in add_file. Python does not use MATLAB error identifiers for document errors, so ValueError with the same message content matches the existing convention (add_file_series already raises plain ValueError where MATLAB raises DID:Document:addFileSeries:notDeclared). Then update +did/document.m's decision_log to say so.
The tests for #68 will not encode the current behaviour on either point — a test that locks in a missing guard is worse than no test.
Activity
stevevanhooser commentedon Sep 8, 2026
Both guards are ported and on
main(981cebc), inb08f507/ #79.1.
Document.__init__validates file declarations._validate_file_declarations(src/did/document.py:972, called from:58) implements all four of MATLAB'slocalValidateFileDeclarationsrefusals — a series declared twice case-insensitively, a series not also infile_list, a series beside aNAME_#entry, and a literalNAME_<n>entry a series would shadow.ValueErrorrather than the four MATLAB identifiers, matching the rest ofdid.document. The check runs on the properties-dict branch as well as the class-definition one.2.
add_filerefuses a series member (src/did/document.py:157), mirroringDID:Document:add_file:isSeriesMember.3. The bridge entries this issue flagged are corrected.
add_file's stale signature note, andis_in_file_list's "Exact match" claim — now recorded as the deviation it always was, with the reason the narrowing is deliberate. So the docstring and the bridge no longer disagree.Verified rather than taken from the commit message: disabling the constructor check turns
TestFileDeclarationValidationred (two named failures); disabling theadd_fileguard turnsTestAddFileRefusesASeriesMember::test_a_member_name_cannot_be_added_directlyred. Full suite green at 693 passed, 10 skipped, 1 xfailed.Left deliberately, both recorded in
did_matlab_python_bridge.yamlrather than silently:str2numevaluates, soNAME_piis shadowed there; Python'sseries_member_ofrequiresisdigit(), so it is not a member here and nothing shadows it. A guard has to match the resolution rule of the language it guards. One-directional cost: a declaration carryingNAME_pibeside aNAMEseries is valid here and would be refused by MATLAB.is_in_file_listis still not ported, by decision rather than oversight — no caller in this repo wants MATLAB's(b, msg, index)shape, andis_series_memberis the public form of the same question. Recorded in the bridge as a deviation.Closing as completed.
Generated by Claude Code
Merge pull request #83 from VH-Lab/claude/matlab-series-fallback-para…