Account import: an archive from a newer Postulo is not refused by format, and fails with a traceback #561
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#561
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found on 2026-09-29 in a read-only review of postulo at
95348908d, comparing the account importer's manifest check with the backup reader's.Checked again on 2026-10-01 against
mainat0a5cad587: still present.The account importer checks only that the manifest has a
formatkey. It never compares the number with the formats it knows, so an archive written by a newer Postulo is read as if it were current. What happens next depends on what the newer format added: a new key on a record ends the import in aTypeErrortraceback, and a new top-level section is dropped without a word while the command reports success. The backup reader already refuses an archive outside the range it reads, with a sentence saying which range that is.core/importer.py:114-127(read_manifest:if "format" not in header), againstcore/backup.py:405-427(read_manifest, which refuses anything outside1 <= fmt <= BACKUP_FORMAT). The records go straight into constructors atcore/importer.py:442(model.objects.create(owner=user, **values)) and in the skills, contacts and companies loops.core/management/commands/import_data.py:35catches onlyArchiveErrorandzipfile.BadZipFile.core/export.py:27-63shows both shapes of change. Format 24 addedesco_urion a skill: a Postulo that reads format 23 passes that key toresume.Skill.objects.create(..., **entry), and Django raisesTypeErrorfor an unexpected keyword argument.import_datadoes not catch it, so the operator sees a traceback rather than "this archive is newer than this Postulo". Format 25 addedremembered_placesat the top of the file: a Postulo that reads format 24 never looks for it, reports the import as done, and the section is gone without a line inreport.skipped. The transaction inload()means the first case leaves the account untouched; the second loses data silently.importer.read_manifest, refuse anyformatthat is not anint(and not abool) from 1 toexport.FORMAT_VERSION, with a sentence that mirrors the backup reader's ("This archive is export format 26; this version of Postulo reads 1 to 25."). Ideally one small helper serves both readers.Done when
tests/test_export.pybuilds an archive whose manifest saysFORMAT_VERSION + 1and asserts thatload()raisesArchiveErrornaming both numbers, and thatimport_dataexits with that message rather than a traceback.formatis a string ortrueis refused the same way.