Stale-FAILED packages: an explicit “Inconsistent” bucket, reconciled from ops#
ops-db-ui #37 (Transfers dashboard rework). Builds on #114 (reset restores package
state) and the data-transfer breadcrumb work (error_context).
Context#
attribute_failed_package charges a failed package to the latest currently-FAILED
related op. A package in state=FAILED with no related op currently FAILED falls
to a residual bucket — historically labelled unknown. On staging ~95 of ~105
failures were this bucket: the failure surface read ~90% “we don’t know where”.
These packages are not failing now — their lifecycle state is stale, left
FAILED after an op was reset/recovered before #114 made reset also restore the
package state. #114 prevents new occurrences; the existing pile is pre-#114 residue
and mostly predates error_context, so the package row often carries no breadcrumb.
Calling this unknown is dishonest twice over: it reads as “unattributable mystery”
when the real meaning is “state disagrees with the operations”, and re-deriving a
pipeline position for a package that may have recovered would assert a live
stuck-point that does not exist.
Decision#
Three coupled changes, all preserving the partition invariant (a package only moves between buckets, never disappears):
Rename the bucket to
inconsistentand surface it as its own explicit, explained group (“state=FAILED, no operation currently failed”), distinct from the genuine per-step failures — not as a funnel step.Breadcrumb-or-inconsistent attribution. In the residual branch, if the package carries an
error_contextwith astep/operation_type, charge that step’s failed count and present it as “last failed at X” — never “stuck at X”. The breadcrumb is read server-side only to pick the cell; the error text is not shipped to the overview payload (consistent with ADR-0002). Packages with no breadcrumb stay ininconsistent.Reconcile, not flip. The admin action on an inconsistent package recomputes its state from its actual operation statuses — transfer+unpack+LTA all complete →
ARCHIVED, mid-pipeline →TRANSFERRING, nothing started →WAITING. Idempotent. The one-off cleanup of the existing ~95 is this same bulk action run once, not a migration or script.
Considered options#
Reconcile from ops (chosen) correctly handles both sub-cases: a stale package that is actually fully archived becomes
ARCHIVED; a mid-pipeline one becomesTRANSFERRING. Honest and self-correcting.Flip to
TRANSFERRING(mirror #114’s op-reset side effect) is less new code but sends already-complete packages bouncing back through the pipeline until a manager re-derivesARCHIVED. Rejected as misleading for the fully-archived sub-case.Positional reconstruction (place a FAILED package at its first-incomplete phase regardless of breadcrumb) asserts a live position for packages that may have recovered. Rejected as dishonest.
Dismiss without state change hides the bad rows while leaving the DB inconsistent. Rejected.
Consequences#
unknown_failed_raw_packages/ theunknownstep label becomeinconsistentacross ops-db-api and the ops-db-ui Failures panel; the reconciliation invariant is re-derived in those terms.A new admin-gated reconcile endpoint (per-package and bulk) is added for
RawDataPackage— the operation type the existing reset endpoints did not cover. Its actions are admin-only (ADR-0002 principle: Observers cannot act).Honest surfacing is achieved by fixing state (reconcile + cleanup), not by relabelling — the bucket drains toward zero rather than being hidden.