Repository navigation
feat(vault): abandon an unaccepted registration - #489
Draft
LKSNDRTMLKV wants to merge 1 commit into
Draft
LKSNDRTMLKV wants to merge 1 commit into
LKSNDRTMLKV wants to merge 1 commit into
Conversation
9 tasks done
LKSNDRTMLKV
force-pushed
the
feat/registry-record-handle
branch
from
October 20, 2026 22:32
be6483e to
1420f86
Compare
LKSNDRTMLKV
force-pushed
the
feat/registry-abandon
branch
from
October 20, 2026 22:33
70a9333 to
9ae6beb
Compare
4 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A registration the registry never accepted is retried with backoff for as long as the passport stays published. When the registry refuses the payload every time, or the product is withdrawn before it was ever registered, that is forever, and the only way off the queue was a database edit. This adds the write that ends it:
POST /vault/api/v1/dpp/{dppId}/registry/abandon, withodal registry abandon <passport-id> --reason "…". It is the "give this one up" write #487 left out on purpose, because it is the more dangerous of the two.Stacked on #487 (base
feat/registry-record-handle), which is stacked on #484 and #479, and it uses core'sfind_submissionfrom odal-node/dpp-core#486. Draft, and not mergeable yet: it builds againstdpp-core0.22.0 on crates.io, which does not have those types, so CI is expected to fail until core releases them and the pins move. It was developed and verified against the local core working tree (04a3843). Migration0044follows #487's0043.Related issue
The open decision in #487: "No 'give this one up' write."
Why this one is dangerous, and what the design does about it
A
pendingrow whose first attempt got through and whose reply was lost looks exactly like one that never did: the registry holds a submission for the passport and this node has no record that it does. Abandoning that row would leave a registration the registry is checking, and may grant, that the node believes it dropped. So the route does not take the operator's word for it, and the write cannot race the drain.The registry is checked. The task list is read through
find_submission, and the request is refused unless all of it was read and none of it reports the unique product identifier the passport was submitted under (NotFound { exhaustive: true }).Found)409, naming its correlation identifierAmbiguous)409, naming the candidatesNotFound { exhaustive: false })422Unsupported)422502, safe to retryA held submission is a
409and not a422because the registration is there, not merely unproven.It cannot race the drain.
abandonis one conditionalUPDATEthat takes a row only while it ispending, has been attempted at least once, and is backed off (next_attempt_at > now()). The drain reads a row while it is due and only a finished attempt pushes it out, so an abandon cannot land between the drain's read and itsregistercall. Asubmittedrow is never abandoned from here: the registry holds it.Nothing the drain writes can resurrect it. Every update the drain makes (
mark_submitted,mark_submitted_noting,mark_deactivated,mark_registered,mark_rejected,mark_attempt_failed) now carrystatus <> 'abandoned',record_handlealready requiredsubmitted, and the due set never offered the row. A pass that read a row before it was abandoned bounces off.Changes
abandoned, kept apart fromrejected: a rejection is the registry refusing a submission it received; this is a person deciding to stop asking about one the registry never accepted. Counted separately inGET /vault/api/v1/registryand in aregistry_outbox_abandonedgauge, and not counted as stalled, so it is not read as still owed and does not ask for a person.abandoned by <actor>: <reason>) and in the audit trail:registry_abandoned, with who, why, how many attempts, and what the row had been saying. Migration0044admits the status and the action, keepingsuspendedand every action written before.200withalreadyAbandoned: truethat asks the registry nothing and writes nothing, so a retry after a lost response does not fail on its own success.404no registration,409a state this does not end, the registry holds a submission, or the row has not been tried / is due now,422not shown absent or a bad reason,501no registry queues,502registry unreadable.409that says so.pending-row paragraph, andCHANGELOG.md.Decisions I would like a second opinion on
pendingrows, only after a failed attempt. A fresh row cannot be abandoned until the drain has tried it once. Someone who published by mistake has to wait for the first failure (seconds to the first backoff, longer as it grows). The alternative, abandoning a due row, is exactly the race the lease prevents.abandonedis a state of its own, not arejectedwith a note. More migration and more surface, but a rejection and a giving-up are different facts, and a count that mixes them tells an operator the registry refused something it never saw.rejectedrows now requeuesabandonedones.Checklist
404/409told apart without asking the registry,502, admin only, malformed reasons), the handle route reading the new status, and the CLIjust lintpasses locallyjust fmtappliedjust testpasses (unit) and the integration suites:just checkexits 0 with 1572 tests, the feature-gated lint exits 0, the vault integration suite passes (508), the outbox suite passes (27), the OpenAPI contract passes (25), andjust openapi-checkpasses with the bundles staged, against the localdpp-coreprintln!/eprintln!/dbg!in service-cratesrc/.envfiles in the diff0044widens the registry status and audit actionCHECKs only; no table or grant changes