# Decision Matrix: SD qualification contract reconciliation

Status: decided
Result: decided; contract-first recommendations adopted
Type: decision-matrix
Updated: 2026-08-13
Next Action: repair or otherwise restore the admitted Controller B read-only terminal-observation path, capture two matching stopped-clean observations for run `efdeaec7-f0e7-4458-8012-7580dab9f18f`, finalize that run without promoting a profile pass, then perform row-by-row migration admission
Campaign: 005-complete-sd-qualifier-gate2-release-plan-acceptance
Parent: work/wp/active/wp-build-standalone-pi-sd-qualification-operator-application.md
Canonical Truth: docs/sd-card-qualification-contract.md

## Decision

Use one concise governing front door to order the existing authorities, define
campaign types and conclusions, and constrain execution. Preserve detailed
technical sources and immutable evidence rather than rewriting or discarding
them. Pause the current live campaign at a task boundary until it is audited
against the contract.

## Options

| Decision | Options considered | Selected | Reason |
| --- | --- | --- | --- |
| Contract structure | Keep distributed rules; rewrite all technical sources; add one governing front door | Governing front door | Removes ambiguity while preserving detailed accepted contracts and history |
| Campaign meaning | One generic “qualification”; explicit family/change/app/support types | Explicit campaign types | Prevents a hotfix or app test from being reported as card-family approval |
| Progress unit | Task/attempt/activity counts; satisfied contract leaf gates | Contract leaf gates | Aligns displayed progress with actual qualification completion |
| Retry behavior | Open-ended attempts; campaign-wide attempt pool; one primary plus one attributed replacement per observation | Bounded per-observation attempts | Stops random-looking trial loops and makes exhaustion conclude `INCONCLUSIVE` |
| Case credit | Planned case label; observed immutable proof | Observed proof | Preserves the selected HPT2 fail-closed evidence rule |
| Mid-campaign application changes | Deploy continuously; forbid every correction; pause and admit a versioned migration | Pause and admit migration | Allows necessary corrections without silently changing evidence meaning |
| Existing evidence | Discard all; reuse all; admit row by row | Row-by-row admission | Preserves valid work without grandfathering incompatible or ambiguous evidence |
| Result model | One campaign badge; separate workflow, gate, sample, family, firmware-change, and app-release results | Separate result layers | Makes “blocked,” “paused,” “card failed,” and “app failed” mean different things |
| Current hotfix campaign | Resume immediately; abort/discard; graceful pause then audit | Graceful pause then audit | Avoids replay or evidence loss while resolving release/state/proof gaps |
| Missing external capability | Work around it inside the app; silently expand firmware scope; declared bounded supporting detour | Declared detour | Keeps application work primary and makes dependencies visible |

## Applied recommendations

1. The application on `srv-rpi00.local` is the operator workflow and system of
   record; supporting scripts and services do not become separate campaigns.
2. A full family campaign follows the ordered intake, baseline,
   initialization, matrix, recovery, endurance, regression, closeout,
   per-card review, and paired-family review phases.
3. The exact-read hotfix campaign is `CHANGE_IMPACT`; it cannot independently
   conclude that the KEXIN family is qualified.
4. No new controller task begins while the current campaign is pausing/paused.
5. The next implementation slice is the executable contract and transition
   fixtures, not another controller case or UI feature.

## Consequences

- The existing 61-step live plan is evidence and implementation input, not the
  final qualification contract merely because it is deployed.
- Existing credited rows remain immutable but receive future completion credit
  only after explicit admission.
- Endurance and family disposition can no longer disappear behind a matrix or
  change-impact campaign result.
- The dashboard can distinguish genuine qualification progress from task
  attempts and recorded activity.
- Contract-version changes become visible migrations or new campaigns instead
  of silent reinterpretations.

## Current campaign checkpoint

At the 2026-08-13 pause request, the website reported campaign
`8c7a2b1a-7e67-48ac-8895-7999d76c4c2a` as `BLOCKED`, the coordinator as
`PAUSING` with desired state `PAUSED`, current node `matrix.s2.b`, and latest
task `CLAIMED` at attempt 3. It displayed 21 of 61 qualification steps and
stated that the already-claimed task may finish but no new task will start.
This establishes the safe audit boundary; it does not disposition that task or
the card.

## Preliminary current-campaign admission audit

| Contract gate | Observed current campaign/application state | Admission |
| --- | --- | --- |
| Campaign type and conclusion | Website identifies the campaign as `change-impact`, but its 61-step plan and traveler still resemble a full qualification | Type is admissible; permitted final conclusion must be narrowed explicitly |
| Frozen executable contract | Plan revision 1 points to the Python template source and has no contract identity/hash. A new unbound application source now validates as `SMI-SDQ-CONTRACT-V1`, SHA-256 `865b478e21ef97f5b0b11a4b7336257e1ad68aa317276348de56e068ff11813e`, with 7/7 focused tests passing | Live plan NOT ADMITTED |
| New-action admission | The page identifies the exact firmware manifest `624eff9a7b9e96e98b959727257441678d2970a94f8bcd8f3ea1b04fdea0e62d` and reports `admitted False` under admission for new actions | No new action may be started |
| Complete required phases | Plan has admission, matrix, recovery, regression, closeout, and disposition; it has no explicit three-wrap per-sample endurance gate | Not a full family qualification |
| Leaf definition completeness | Schema validates key, label, progress, and action; it does not require the contract's actor, prerequisites, expected duration, timeout, outputs, immutable evidence, result, or replacement-attempt rules | NOT ADMITTED |
| Bounded attempts | Current template budgets S0 `9`, S1-S3 `30`, S4 `3`, and S5-S6 `5` attempts per case/role; the page reports 187 recorded runs for 21 satisfied leaves | Requires replacement with per-observation primary/replacement limits before resume |
| Qualification progress | 21/61 gates: admission 1/1, matrix 13/42, recovery 5/10, regression 2/2, closeout 0/4, disposition 0/2 | Incomplete |
| Evidence integrity | Results page exposes immutable paths, sizes, SHA-256 values, and producer identities | Candidate for row-by-row admission; not sufficient by itself |
| Runtime consistency | Coordinator is `PAUSED` with desired state `PAUSED`, no claimed or pending tasks, and current node B S2. The earlier A run is no longer open. One B S2 run remains `RUNNING` in the database after its exact Stop was acknowledged because independent terminal observation failed closed. | NOT ADMITTED; supporting read-only observation repair required |
| No-replay safety | Start operation `dd0eaece-bb97-4b67-b423-85a16dfca9b3` was reconciled read-only as acknowledged. One separately minted Stop operation `f1492992-f957-4baa-a676-471b8c2f995f` was published once and acknowledged; automatic replay is false. | Preserve; do not publish either command again |

**Current disposition:** hold the campaign at the pause boundary. Do not resume
or close it as passed until the executable contract exists and the row-by-row
admission audit determines whether the change-impact work can continue. The
current evidence does not support a card-family qualification conclusion.

Read-only host reconciliation at `2026-08-13T09:10-07:00` confirmed that the
durable campaign-task runner itself is active, but the expected transient
Controller A and B runner units named by the observers are both inactive/dead.
Subsequent ledger reconciliation established that the A proving run is no
longer open and that the only open hardware run is Controller B S2
`efdeaec7-f0e7-4458-8012-7580dab9f18f`.

The pause cleanup used the existing web authorization and immutable command
workflow. Its Start was reconciled as acknowledged without replay. Stop intent
`f1492992-f957-4baa-a676-471b8c2f995f` was then minted for that exact run,
published once, and acknowledged as `MAILBOX_COMMAND_ACKNOWLEDGED`; no command
may be replayed. At `2026-08-13T09:21-07:00`, the coordinator was `PAUSED`, no
task was claimed or pending, and the B run was still represented as `RUNNING`.
The required independent `STOPPED_CLEAN` observation then failed closed with
`The J-Link read-only operation failed closed.` The remote-server and probe
services were active, so restarting them without diagnosis would be a blind
support change. This is now an explicit supporting detour: restore the
admitted read-only observation path, obtain two matching terminal
observations, and run the non-promoting finalizer. Do not resume the campaign
or admit this row into contract progress as part of that cleanup.
