# SD Qualifier Separated Card Tracks and Paired Gate Evidence

Status: complete
Result: production implementation verified; browser visual review pending
Type: evidence
Updated: 2026-08-06
Next Action: perform desktop/mobile visual inspection when the browser connection is available
Campaign: 006-start-clean-contract-bound-sd-qualification-campaign
Parent: work/tickets/ticket-separate-physical-card-tracks-and-add-final-paired-qualification-gate.md

## Outcome

The physical-card status surface no longer presents historical shared
`A/B qualification` or `Controller A qualification` scope text as the title of
one physical card's phase. Every phase title now identifies the exact sample
and controller, while the immutable original name remains visible as
`Shared source campaign` provenance.

After the chronological card phases, a final A/B qualification gate shows the
two exact physical-card tracks separately. Membership comes only from explicit
`CampaignSample` relationships. The gate fails closed for missing roles or
attention states, waits while either track is incomplete, becomes ready only
after both latest assignments pass without active work, and shows approved
only when both required roles pass the same approved shared campaign.

No campaign, assignment, run, CID capture, evidence, or historical title was
rewritten. No schema migration or data backfill was required.

## Source and deployed image

- Repository: `PC-Redemption/sd-qualification`
- Feature commit: `8299ec1febfded6ed63d95b62bfb437b0c6a323e`
- Pull request: `https://github.com/PC-Redemption/sd-qualification/pull/37`
- Merge commit: `f8b6f08eb9bca947bbffbb8ca8d37ed555174333`
- Deployed tag: `srv-rpi00-sd-qualifier:card-gate-f8b6f08`
- Deployed image:
  `sha256:358b3fce06d8e6b5cd7927a9595bc1e5346d02d9cffe00eafe3ea724d239ce9d`
- Web start: `2026-08-06T16:13:25.275803259Z`

## Automated validation

- Focused paired-gate and card-isolation regressions: 5/5 passed.
- Exact final ARM Linux image: 246/246 tests passed in 58.094 seconds.
- Django system check: no issues.
- Python compile and Git whitespace checks passed.

Regression coverage includes card-specific label projection, exact peer
selection, unrelated-card exclusion, missing required roles, waiting and
attention states, approval requiring two exact passes, dashboard labels, and
the existing no-cross-card-active-checkpoint invariant.

## Production render verification

An authenticated render check against the deployed web image returned HTTP 200
for the dashboard and both exact card pages.

- Dashboard links remained separate for `/samples/3/qualification/` and
  `/samples/4/qualification/`.
- F1-A1 rendered seven chronological phase titles ending in
  `F1-A1 / Controller A`.
- F1-B1 rendered seven chronological phase titles ending in
  `F1-B1 / Controller B`, including the source campaign whose historical name
  says Controller A.
- Both pages rendered separate `Card A · F1-A1` and `Card B · F1-B1` gate
  members.
- The final A/B gate rendered after the last card phase and showed `WAITING`.
- Shared source campaign provenance remained available.

The in-app browser runtime reported no available browser instance after
deployment. Therefore desktop/mobile visual layout and overflow inspection are
explicitly pending rather than claimed from the authenticated HTML render.

## Runtime continuity

Deployment recreated only the web service with `--no-deps`. The command and
notification workers retained their existing container IDs, start times, and
qualified image
`sha256:4237e644eec42d742be75942a9ff2f8013812ebc309e0f416f306b70aefbd9c7`.
All four Controller A systemd services remained active.

The public health endpoint returned HTTP 200 with database, storage, clock,
and worker health true. The exact F1-A1 run remained `RUNNING` in campaign
`648ba10c-0bc0-4319-8089-78a0cce287ce` at S4 `BEFORE 44640`. An expected
HPT2 command-adapter container was active during the final check; it was not
restarted or modified by the web-only deployment.
