# SD qualifier current-campaign selection deployment — 2026-08-11

Status: verified
Scope: web application and one-time campaign-lineage reconciliation only

## Outcome

The fixed-card dashboard no longer selects a campaign from generic event
recency. It resolves the active campaign lineage deterministically, follows a
single active successor, and fails closed when active branches are ambiguous
or when a displaced predecessor still owns an open run. The dashboard and card
traveler expose the selected campaign explicitly; unresolved lineage exposes
the candidate branches and authorizes no controller action.

The existing relationship was reconciled transactionally after exact
preconditions confirmed both campaigns, the F1-A1/F1-B1 assignments, and zero
open runs:

- predecessor: `648ba10c-0bc0-4319-8089-78a0cce287ce` (2026-08-06);
- successor: `8c7a2b1a-7e67-48ac-8895-7999d76c4c2a` (2026-08-10);
- successor `based_on_campaign_id` remained the predecessor and
  `supersedes_campaign_id` was set to the predecessor;
- append-only event `campaign.current-assignment-reconciled` sequence `316`
  records the IDs, reason, source commit, and that no controller action was
  authorized;
- reconciliation script SHA-256:
  `bb2364dc95bbd2334b3e349a2effc535aa5bcb3305988856c16ae05cffaaf558`.

No schema migration, firmware build, controller command, or media operation was
needed.

## Implementation and validation

- Selection and ambiguity implementation commit:
  `c8caaa8619dbef0173c29cdd7c6b48fe80798229`.
- Campaign progress API serialization correction found during live browser
  verification: `a7aa0ec9890def02f62357d0a3147c22332dde01`.
- Dashboard supervisor-banner alignment correction found from operator visual
  evidence: `d37c8546b3d9e39b938475fa8e53f4945a1feca6`. The banner now consumes the
  same resolved card snapshots as the active-card tiles instead of the first
  20 nonhealthy supervisor rows across campaign history.
- Card-traveler recovery projection correction found from operator visual
  evidence: `47b1fb52f171acf87b4d8aadaa120145a6c82bcc`. F1-A1 step 5 now derives its
  status from its own five required current-campaign recovery records instead
  of requiring an execution-plan row. Evidence remains card-scoped, so F1-B1
  cannot borrow Controller A results. Steps 6 and 7 remain `NOT RUN` because no
  representative-regression evidence, post-campaign scrub, or final
  post-campaign observation exists.
- Regression coverage includes successor-over-stale-predecessor selection,
  ambiguous sibling successors, a displaced predecessor with an open run, and
  event-API serialization with a supervisor projection.
- Final exact ARM64 suite: `334/334` passed; Django system check reported no
  issues.
- Final image:
  `srv-rpi00-sd-qualifier:qualification-card-current-47b1fb5`, image ID
  `sha256:8cd8b7717ef9496b517984b46c08fb36964ad03ed2dd902d361cda6e56de8d03`.

## Deployment verification

The web service alone was recreated and reached Docker `healthy`. Its image
labels bind release `qualification-card-current-47b1fb5` to source
`47b1fb52f171acf87b4d8aadaa120145a6c82bcc`. The command worker, notification
worker, and qualification watchdog retained their original container IDs and
their `2026-08-11T14:13:18Z` start times. The pre-existing readiness result
continues to report `workers=false` because `qualification-host-observer` is
stale; this web rollout did not create or clear that condition.

Authenticated Playwright verification established:

- F1-A1 and F1-B1 dashboard cards both bind
  `data-current-campaign-id=8c7a2b1a-7e67-48ac-8895-7999d76c4c2a`;
- the global qualification-blocked banner does not show the superseded Aug. 6
  supervisor rows; no banner is currently rendered because both Aug. 10
  supervisor projections are `HEALTHY`;
- the campaign table reports the same Aug. 10 campaign as `RUNNING`, with
  `20 / 24 complete`, `0 open`, and `4 needs attention`;
- both card travelers mark the Aug. 10 campaign current and display their own
  Controller A and Controller B next actions;
- F1-A1 step 5 reports `PASS`, an actual Aug. 11 completion time, and all five
  required recovery cases; steps 6 and 7 report `NOT RUN`; F1-B1 step 5 also
  remains `NOT RUN` because it has no card-specific recovery evidence;
- the campaign page shows audit event `316` and the same `20/24` counts;
- two event refresh requests after sequence `316` returned HTTP `200`, with no
  browser console errors;
- the dashboard has no page-level horizontal overflow at 390 CSS pixels.

The temporary browser-verification users and remote temporary scripts were
removed. Successor campaigns that populate `based_on_campaign` or
`supersedes_campaign` now update fixed-card status automatically; ambiguous
lineage remains visible and blocked for operator review.
