Your career: a driving licence section, with the categories as codes, and never the licence number #691

Closed
opened 2026-10-03 01:59:04 +00:00 by tiagoagueda · 1 comment
Owner

A career record has nowhere to say that its owner can drive, and for many roles (field work, delivery, a company car) a CV has to. The maintainer's list asks for it under Your career: "Add driving license." A licence is personal data and an identifier people are defrauded with, so this issue also draws the line on what is not kept.

What exists today

  • No licence anywhere: no model, field or string in src/. The nearest thing is a Certification (src/postulo/resume/models.py:215-228: name, issuer, dates, a credential link), usable as a workaround: “Driving licence B”, issued by whoever.
  • The career is a registry of sections (model, form, SECTIONS/OVERVIEW_ORDER in resume/registry.py:28-91, the CV picker in documents/forms.py:386-432, documents/rendering.py:41-49 and :336-385, a branch per kind in both base themes, core/export.py:343-415, the candidate file's KINDS in resume/candidate.py:176-276); #687 lists every one. Career entries are on a CV only when the person adds them; #308's print choice (documents/printing.py) is for the profile's details and does not apply.
  • Europass. The Europass CV v3 schema (European Commission, DG EMPL, Europass2 CV XML Schema Documentation 3.0.0, 17 February 2020) has /CandidateProfile/Licenses/License with LicenseTypeCode (a code list "based on Directive 2006/126/EC"; examples AM, A1, A2, A), LicenseName, FirstIssuedDate and FreeFormPeriod; the older SkillsPassport format has Skills/Driving with a code list of its own. Postulo's candidate reader reads neither (Licenses is in neither READ nor QUIET, src/postulo/plugins/europass/candidate.py:136-151), so a file that has one gets “Some sections of the file have nowhere to go in Postulo yet: Licenses” (:622-645); the older reader never mentions driving; the wiki lists "driving licences" among what a Europass CV holds that Postulo cannot (Your-career-record.md:415-418).
  • Countries: core/phones.py COUNTRIES (241 rows, English names; country_name :403, country_choices :417), used by the postal form (core/postal.py:103,355). No career API, search or core/gdpr.py change is needed (the processing record is drawn from plugins).

What the law says (read on 2026-10-03 from the Official Journal texts on the Publications Office's server).

  • Directive 2006/126/EC of 20 December 2006 (OJ L 403, 30.12.2006, p. 18; "Text with EEA relevance"), Article 4, defines fifteen categories: AM, A1, A2, A, B1, B, BE, C1, C1E, C, CE, D1, D1E, D, DE; B1 is optional for a Member State. The Union model licence prints "national categories … in a different type from harmonised categories" (Annex I), so a licence can carry letters this list lacks.
  • Directive (EU) 2025/2205 of 22 October 2025 (OJ L, 5.11.2025) repeals it "with effect from 26 November 2029", except Article 6(4), point (c), of the 2006 act, which goes on 26 November 2027 (Article 30); Member States adopt the measures by 26 November 2028 and apply them from 26 November 2029 (Article 29); it entered into force on the twentieth day after publication (Article 31). Its Article 6(1) keeps the same fifteen categories. It adds a mobile (digital) licence (Article 5).
  • The Union model's fields (Annex I): 1 surname, 2 other name(s), 3 date and place of birth, 4a issue, 4b expiry, 4c issuing authority, 5 licence number, 6 photograph, 7 signature, 8 residence (optional), 9 categories, 10 date of first issue of each category, 11 expiry of each category, 12 codes. In the 2006 text, codes 01, 02, 03 and 05 are listed under "DRIVER (Medical reasons)" (sight correction, hearing aid, limb prosthesis, limited use); the 2025 Part E was not read in full.
  • GDPR Article 9(1) lists the special categories: racial or ethnic origin, political opinions, religious or philosophical beliefs, trade union membership, genetic data, biometric data for unique identification, data concerning health, sex life or sexual orientation. A category such as B is not among them; medical-reason restriction codes are data concerning health.
  • Outside the Union the letters differ (the United Kingdom, Switzerland, Canada, the United States and others have their own); not verified here.

Checked against main at f09b6010b.

Proposal

  1. A section, “Driving licences” (DrivingLicence, a ResumeItem, slug driving-licence), after Certifications on the career page and in the CV picker; a person may hold several (a Portuguese and a British licence are two entries). Fields: country (ISO 3166-1 alpha-2 from core/phones.py, optional), categories (the fifteen, stored as codes in the Directive's order), other_categories (free text for national letters or classes, up to 100 characters), first_issued_on and expires_on (optional).
  2. One table of categories in resume/driving.py: the fifteen codes in order, each with a one-line summary in Postulo's own words (“B — cars and light vans”) as help text. The form is a fieldset of checkboxes with a legend; the archive importer and candidate reader refuse a code not in the table. No category implies another: a person who holds A ticks A, and the CV prints what is ticked.
  3. A CV prints one line, “Driving licence: B, A2 (Portugal)”, in the document's language: the codes in order, the country in brackets only if filled, never a date. Both base themes, the plain text, the Word file and the preview print the same; an entry prints only on a CV it is added to.
  4. Never kept: no licence number (fields 5 and 4d), photograph, signature, residence, or restriction code. A test asserts the model's field list so that a column of that kind fails it, and docs/THREAT-MODEL.md says why beside the home address.
  5. Carried like any career entry: archive (driving_licences in RESUME_FIELDS and RESUME_MODELS; format one more than whatever it is when the commit lands; the importer's section map and the CV entry's kind name drivinglicence), candidate file (KINDS; format one more; identity is country plus categories), translatable.py (translates nothing: a code is a code), seed data. No page outside the registry's.
  6. Europass reading Licenses/License (code, first-issued date, free-form validity) is a follow-up commit; the Record surface would gain a driving_licences list (Writing-a-plugin.md, tests/test_wiki_surface.py).
  7. Wiki: Your career record gets Driving licences and says what is never kept.

Decisions

  • A section of its own, or a kind of Certification. Recommended: its own. A licence's categories are a closed list a CV prints compactly, and #686 asks that a certification's issuer be a company with a qualifying NACE code, which a national licensing authority is not. #695 lists “a licence” under Certification; a driving licence is the exception.
  • What is stored. Recommended: the codes, the country, two optional dates. The number would be the most valuable line in a breach and does nothing for a CV; field 12 would be health data (Article 9(1)).
  • Per-category dates. The Union model has them (fields 10 and 11); Europass has one FirstIssuedDate per licence. Recommended: one of each per entry; per-category only if asked.
  • Print the country. A British or Brazilian B is not an EU B; most CVs just say “B”. Recommended: print it when filled, so a person leaves it blank to leave it off. A name in the document's language needs a source Postulo lacks (the limit #679 and #680 share; #689's module would give it).
  • Outside the Union. Recommended: the fifteen as checkboxes and other_categories as text, no registry of other countries; a country's own list could become a plugin kind later (the shape of #305's service registry).
  • Which directive to name. The 2006 act is in force until 26 November 2029 and is what Europass's own code list cites; the 2025 act repeals it and keeps the fifteen. Recommended: the wiki names both; if a sixteenth category ever comes, the table is the one place to change.
  • An expiry reminder (the machinery exists, #316) and a scan of the licence (a document upload, #669). Recommended: neither here.

Prior art

Standards, not code: Directive 2006/126/EC Article 4 and Annex I; Directive (EU) 2025/2205 Article 6 and Annex I; the Europass CV v3 License and its code list; GDPR Article 9(1). No library was looked for: the data is fifteen codes and their summaries, with no behaviour to borrow. The two hand-written standards (applications/ical.py, documents/docx.py) are file formats; this is a list.

Builds on

#687 (the section registry's checklist), #308 (what a CV prints), #131 (translations of entries), #181 (the candidate file), #306 (countries), #54 and #244 (the Europass import).

Needed by

Nothing. A later Europass commit reads Licenses into it.

Done when

  • A new tests/test_driving_licences.py: the table holds the fifteen codes in order; several categories save and show in order; a code outside the table is refused by the form, the archive importer and the candidate reader; other_categories is bounded; two licences for one person are allowed; the model has no field for a number, photograph, signature, residence or restriction code (the field list is asserted).
  • tests/test_career_sections.py passes with the new anchor and count; the CV picker offers the entry; tests/test_documents.py and tests/test_document_formats.py show the same line in both themes, the plain text, the Word file and the preview, the country only when filled and no date ever; a CV without the entry prints nothing about driving.
  • tests/test_export.py: the archive carries driving_licences at the new format, a restore brings them back and a CV holding one keeps it; tests/test_candidate_file.py holds the new fingerprint and review rows for a good and a refused licence; tests/security/test_candidate_file.py a hostile or over-long value.
  • tests/security/test_isolation_sweep.py covers another account's licence; tests/test_account_deletion.py removes them with the account. New strings (the title, fifteen summaries, labels, the CV line) in the working catalogues (en-GB, fr-FR, pt-PT as drafts) and Weblate (#349); the wiki, docs/THREAT-MODEL.md and CHANGELOG (ends (#N)) in the same commit.

From the list of improvements of 2026-10-02.

A career record has nowhere to say that its owner can drive, and for many roles (field work, delivery, a company car) a CV has to. The maintainer's list asks for it under Your career: "Add driving license." A licence is personal data and an identifier people are defrauded with, so this issue also draws the line on what is not kept. ## What exists today - **No licence anywhere**: no model, field or string in `src/`. The nearest thing is a *Certification* (`src/postulo/resume/models.py:215-228`: name, issuer, dates, a credential link), usable as a workaround: “Driving licence B”, issued by whoever. - **The career is a registry of sections** (model, form, `SECTIONS`/`OVERVIEW_ORDER` in `resume/registry.py:28-91`, the CV picker in `documents/forms.py:386-432`, `documents/rendering.py:41-49` and `:336-385`, a branch per kind in both base themes, `core/export.py:343-415`, the candidate file's `KINDS` in `resume/candidate.py:176-276`); #687 lists every one. Career entries are on a CV only when the person adds them; #308's print choice (`documents/printing.py`) is for the profile's details and does not apply. - **Europass.** The Europass CV v3 schema (European Commission, DG EMPL, *Europass2 CV XML Schema Documentation* 3.0.0, 17 February 2020) has `/CandidateProfile/Licenses/License` with `LicenseTypeCode` (a code list "based on Directive 2006/126/EC"; examples `AM`, `A1`, `A2`, `A`), `LicenseName`, `FirstIssuedDate` and `FreeFormPeriod`; the older SkillsPassport format has `Skills/Driving` with a code list of its own. Postulo's candidate reader reads neither (`Licenses` is in neither `READ` nor `QUIET`, `src/postulo/plugins/europass/candidate.py:136-151`), so a file that has one gets “Some sections of the file have nowhere to go in Postulo yet: Licenses” (`:622-645`); the older reader never mentions driving; the wiki lists "driving licences" among what a Europass CV holds that Postulo cannot (`Your-career-record.md:415-418`). - **Countries**: `core/phones.py` `COUNTRIES` (241 rows, English names; `country_name` `:403`, `country_choices` `:417`), used by the postal form (`core/postal.py:103,355`). No career API, search or `core/gdpr.py` change is needed (the processing record is drawn from plugins). **What the law says** (read on 2026-10-03 from the Official Journal texts on the Publications Office's server). - **Directive 2006/126/EC** of 20 December 2006 (OJ L 403, 30.12.2006, p. 18; "Text with EEA relevance"), **Article 4**, defines fifteen categories: **AM, A1, A2, A, B1, B, BE, C1, C1E, C, CE, D1, D1E, D, DE**; B1 is optional for a Member State. The Union model licence prints "national categories … in a different type from harmonised categories" (Annex I), so a licence can carry letters this list lacks. - **Directive (EU) 2025/2205** of 22 October 2025 (OJ L, 5.11.2025) repeals it "with effect from 26 November 2029", except Article 6(4), point (c), of the 2006 act, which goes on 26 November 2027 (Article 30); Member States adopt the measures by 26 November 2028 and apply them from 26 November 2029 (Article 29); it entered into force on the twentieth day after publication (Article 31). Its **Article 6(1)** keeps the same fifteen categories. It adds a mobile (digital) licence (Article 5). - **The Union model's fields** (Annex I): 1 surname, 2 other name(s), 3 date and place of birth, 4a issue, 4b expiry, 4c issuing authority, 5 **licence number**, 6 photograph, 7 signature, 8 residence (optional), 9 categories, 10 date of first issue **of each category**, 11 expiry of each category, 12 **codes**. In the 2006 text, codes 01, 02, 03 and 05 are listed under "DRIVER (Medical reasons)" (sight correction, hearing aid, limb prosthesis, limited use); the 2025 Part E was not read in full. - **GDPR Article 9(1)** lists the special categories: racial or ethnic origin, political opinions, religious or philosophical beliefs, trade union membership, genetic data, biometric data for unique identification, data concerning health, sex life or sexual orientation. A category such as B is not among them; medical-reason restriction codes are data concerning health. - Outside the Union the letters differ (the United Kingdom, Switzerland, Canada, the United States and others have their own); **not verified here**. Checked against `main` at `f09b6010b`. ## Proposal 1. **A section, “Driving licences”** (`DrivingLicence`, a `ResumeItem`, slug `driving-licence`), after Certifications on the career page and in the CV picker; a person may hold several (a Portuguese and a British licence are two entries). Fields: `country` (ISO 3166-1 alpha-2 from `core/phones.py`, optional), `categories` (the fifteen, stored as codes in the Directive's order), `other_categories` (free text for national letters or classes, up to 100 characters), `first_issued_on` and `expires_on` (optional). 2. **One table of categories** in `resume/driving.py`: the fifteen codes in order, each with a one-line summary in Postulo's own words (“B — cars and light vans”) as help text. The form is a fieldset of checkboxes with a legend; the archive importer and candidate reader refuse a code not in the table. No category implies another: a person who holds A ticks A, and the CV prints what is ticked. 3. **A CV prints one line**, “Driving licence: B, A2 (Portugal)”, in the document's language: the codes in order, the country in brackets only if filled, never a date. Both base themes, the plain text, the Word file and the preview print the same; an entry prints only on a CV it is added to. 4. **Never kept**: no licence number (fields 5 and 4d), photograph, signature, residence, or restriction code. A test asserts the model's field list so that a column of that kind fails it, and `docs/THREAT-MODEL.md` says why beside the home address. 5. **Carried like any career entry**: archive (`driving_licences` in `RESUME_FIELDS` and `RESUME_MODELS`; format one more than whatever it is when the commit lands; the importer's section map and the CV entry's kind name `drivinglicence`), candidate file (`KINDS`; format one more; identity is country plus categories), `translatable.py` (translates nothing: a code is a code), seed data. No page outside the registry's. 6. **Europass** reading `Licenses/License` (code, first-issued date, free-form validity) is a follow-up commit; the `Record` surface would gain a `driving_licences` list (`Writing-a-plugin.md`, `tests/test_wiki_surface.py`). 7. **Wiki**: *Your career record* gets *Driving licences* and says what is never kept. ## Decisions - **A section of its own, or a kind of Certification.** **Recommended:** its own. A licence's categories are a closed list a CV prints compactly, and #686 asks that a certification's issuer be a company with a qualifying NACE code, which a national licensing authority is not. #695 lists “a licence” under Certification; a driving licence is the exception. - **What is stored.** **Recommended:** the codes, the country, two optional dates. The number would be the most valuable line in a breach and does nothing for a CV; field 12 would be health data (Article 9(1)). - **Per-category dates.** The Union model has them (fields 10 and 11); Europass has one `FirstIssuedDate` per licence. **Recommended:** one of each per entry; per-category only if asked. - **Print the country.** A British or Brazilian B is not an EU B; most CVs just say “B”. **Recommended:** print it when filled, so a person leaves it blank to leave it off. A name in the document's language needs a source Postulo lacks (the limit #679 and #680 share; #689's module would give it). - **Outside the Union.** **Recommended:** the fifteen as checkboxes and `other_categories` as text, no registry of other countries; a country's own list could become a plugin kind later (the shape of #305's service registry). - **Which directive to name.** The 2006 act is in force until 26 November 2029 and is what Europass's own code list cites; the 2025 act repeals it and keeps the fifteen. **Recommended:** the wiki names both; if a sixteenth category ever comes, the table is the one place to change. - **An expiry reminder** (the machinery exists, #316) and **a scan of the licence** (a document upload, #669). **Recommended:** neither here. ## Prior art Standards, not code: Directive 2006/126/EC Article 4 and Annex I; Directive (EU) 2025/2205 Article 6 and Annex I; the Europass CV v3 `License` and its code list; GDPR Article 9(1). No library was looked for: the data is fifteen codes and their summaries, with no behaviour to borrow. The two hand-written standards (`applications/ical.py`, `documents/docx.py`) are file formats; this is a list. ## Builds on #687 (the section registry's checklist), #308 (what a CV prints), #131 (translations of entries), #181 (the candidate file), #306 (countries), #54 and #244 (the Europass import). ## Needed by Nothing. A later Europass commit reads `Licenses` into it. ## Done when - A new `tests/test_driving_licences.py`: the table holds the fifteen codes in order; several categories save and show in order; a code outside the table is refused by the form, the archive importer and the candidate reader; `other_categories` is bounded; two licences for one person are allowed; **the model has no field for a number, photograph, signature, residence or restriction code** (the field list is asserted). - `tests/test_career_sections.py` passes with the new anchor and count; the CV picker offers the entry; `tests/test_documents.py` and `tests/test_document_formats.py` show the same line in both themes, the plain text, the Word file and the preview, the country only when filled and no date ever; a CV without the entry prints nothing about driving. - `tests/test_export.py`: the archive carries `driving_licences` at the new format, a restore brings them back and a CV holding one keeps it; `tests/test_candidate_file.py` holds the new fingerprint and review rows for a good and a refused licence; `tests/security/test_candidate_file.py` a hostile or over-long value. - `tests/security/test_isolation_sweep.py` covers another account's licence; `tests/test_account_deletion.py` removes them with the account. New strings (the title, fifteen summaries, labels, the CV line) in the working catalogues (en-GB, fr-FR, pt-PT as drafts) and Weblate (#349); the wiki, `docs/THREAT-MODEL.md` and CHANGELOG (ends `(#N)`) in the same commit. From the list of improvements of 2026-10-02.
tiagoagueda added this to the 0.6.0 milestone 2026-10-03 01:59:04 +00:00
Author
Owner

Landed on main with a green Forgejo CI run. Its entry in the CHANGELOG's Unreleased section ends in (#691).

Landed on main with a green Forgejo CI run. Its entry in the CHANGELOG's Unreleased section ends in (#691).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo#691
No description provided.