Skip to content

chore(deps)!: repin core 0.21.0, archive→retire - #358

Draft
LKSNDRTMLKV wants to merge 4 commits into
mainfrom
chore/repin-core-0-21-0
Draft

LKSNDRTMLKV wants to merge 4 commits into
mainfrom
chore/repin-core-0-21-0

Conversation

@LKSNDRTMLKV

@LKSNDRTMLKV LKSNDRTMLKV commented Sep 17, 2026

Copy link
Copy Markdown
Member

🚧 Draft until dpp-core 0.21.0 is on crates.io. The pin in
[workspace.dependencies] is already 0.21.0, so CI is red until that version
publishes — there is nothing to fix in the meantime. Verified green locally
against the unreleased core via just core-local: just check, 1251/1251.

Repins to dpp-core 0.21.0 and carries the two renames that version forces, plus
the gate that stops the third one happening again.

The problem this ends

archive named three unrelated things in this workspace:

What it was What it is now
A terminal lifecycle status, reached once a record stops changing archived retired
The ESPR Art. 10(4) copy held by an Art. 2(32) independent provider ArchivePort, ARCHIVE_S3_*, trustMode.archive back-up copyBackupCopyPort, BACKUP_S3_*, trustMode.backup
EN 18221:2026 clause 4.2 — historical versions of a passport that is still live PassportVersion, ArchivingPassportRepo, GET /versions unchanged — this one keeps the word

The third is the one the standard defines, and it is one of the six cited by
Commission Implementing Decision (EU) 2026/1736. While all three wore the word,
anyone mapping this system onto EN 18221 by name ticked a box that was not
ticked — which is exactly how the clause 4.2 gap survived: the name looked
taken. #352 built the thing; this makes the name true.

Nothing was removed. Archiving is a live obligation and it keeps its word.
What it no longer shares is a wire surface with two other concepts.

Two further uses are compound, unambiguous in context, and deliberately
untouched: the keystore's archived keys, and a seal's archival timestamp
(ETSI's own term). So is retire on facilities and operator identifiers — a
different object, and a retire-not-delete record rather than a status.

Breaking surface

Kind Was Is
Route POST /vault/api/v1/dpp/{dppId}/archive POST …/retire
operationId archiveDpp retireDpp
Status wire value "archived" "retired" — and "archived" is refused with a message that says where the word went
Event subject dpp.passport.archived dpp.passport.retired
Audit action "archived" "retired" (new entries only — see below)
Node state trustMode.archive trustMode.backup
Env ARCHIVE_S3_* BACKUP_S3_*
CLI odal passport archive odal passport retire
Core types ports::archive::ArchivePort + 4 types ports::backup::BackupCopyPort + 4 (core #292)

🚨 The migration rewrites one table and deliberately not the other

ops/pg/0041_retired_status.sql.

passport.status is current state, so it is rewritten and the CHECK narrows
onto retired.

passport_audit.action is history and is not rewritten. action,
prevStatus and newStatus are all inside the hashed payload
(dpp_types::audit, chained since 0015), so an UPDATE would break
verify_audit_chain from the edited row onward and every later entry would
read as tampered
. So the CHECK only widens: retired is added, archived
stays. One permanently permitted legacy value, spent to keep the trail
verifiable. An entry that says archived is a true record of a transition
performed while that was the word.

What the rename turned up on its own

The CLI called a superseded passport "retired". Supersession.retired, and
Retired: <id> in the output of both amend and supersede, for a record
whose status is superseded. Harmless while retired meant nothing; the moment
it became a status, that line names the wrong one. Renamed to superseded
throughout, which is what the field's own doc already said it held.

Two stale ESPR citations. s3_archive.rs and dpp-types::snapshot both
attributed the back-up copy to Art. 13. Art. 13 is the registry. The
obligation is Art. 10(4), which core's own port module has said for some time.

A requirement stated on a method that cannot express it.
ArchivePort::update_archive required the provider to "store the new version
while preserving the full version history"
, which reads as clause 4.2 already
being satisfied by wiring a back-up adapter. It never was.

🚨 It is not, however, a duty on the wrong actor — my first reading of this
was wrong and is corrected here and in core #292.
EN 18221's Annex ZA puts
ESPR Art. 10(4) at clauses 4.3 and 4.5, not 4.2, and clause 4.2 expects archived
versions to be held by the back-up provider as well as by the main one. A
provider is not exempt. What separates the two is shape: BackupCopyPort
carries one copy per passport and no series at all, so nothing about clause 4.2
is expressible through it, whoever owes it.

The corrected wording is applied at every place this repo states the
distinction — s3_backup.rs, PassportVersionStore, the vault service's
versions field, ops/pg/0041 and the CHANGELOG entry — so the looser version
is not the one somebody finds first. ops/pg/0040's header still contrasts this
table with status = 'archived', a status that no longer exists; an applied
migration cannot be edited without breaking sqlx's checksum, so 0041 carries
that correction instead.

The gate

scripts/vocabulary-check.sh, wired into just check. Three absolute rules —
no allow-list, because clause 4.2 archiving needs none: it is served at
/versions and names no route, path file or subject after itself.

  1. no route path contains archiv
  2. no api/paths/ file name contains archiv
  3. no event subject contains archiv

scripts/vocabulary-check.test.sh reintroduces each collision in a synthetic
tree and asserts the gate refuses it, plus a clean-tree case so the other four
cannot pass against a gate that rejects everything. 5/5, and the four rejections
were watched to fail before being trusted.

Verification

  • just check — green, 1251 tests, against ../dpp-core at the 0.21.0
    content via just core-local.
  • just openapi-bundle — re-bundled; openapi.bundled.{yaml,json} carry
    retireDpp and no stale archived status.
  • Docker tiers not run locally (no Docker on this machine) — CI is the only
    place those execute, and it cannot run until core publishes.

Cargo.lock is deliberately untouched

just core-local rewrites it to point every dpp-* crate at the local path,
with the registry checksums stripped. Committing that would break CI in a way
that reads as a dependency problem. It is restored to its main contents here,
so it is stale by one version — harmless, because nothing in CI builds
--locked, and it cannot be made correct before 0.21.0 has a checksum on
crates.io.

Release-day order

  1. dpp-core 0.21.0 publishes (core fix(seal): read the seal mode from the backend, not a literal #287 + feat(seal): report the level a seal actually came back at #292).
  2. CI here goes green with no change; un-draft, label review-ready.
  3. Merge, release the engine.
  4. Re-sync the vendored spec in the docs site and merge docs(engine): tell retirement and archiving apart dpp-web#21,
    which is held for the same reason.

@LKSNDRTMLKV

Copy link
Copy Markdown
Member Author

Tracked alongside this: #359 sets out what else 0.21.0 lands here — chiefly core #283's registry contract, which changes REGISTRATION_PATH and adds submission caps we do not enforce. None of it is this PR's scope; this one stays the rename plus the repin.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant