Skip to content

[PLAN 16] Zbudować dwuetapowy core prepare/confirm zapisu #16

Description

@KeyffMS

Cel

Zaimplementować i w pełni przetestować WriteCoordinator oraz prawdziwie dwuetapową ścieżkę prepare/confirm/commit dla pojedynczego zapisu, pozostawiając funkcję nieosiągalną z produkcyjnego composition root do ukończenia #22 i #23.

Granica etapu

Porty i capability

WriteCoordinator zależy od:

  • ReadBusPort;
  • WriteBusPort;
  • AuditPort;
  • ProfileTrustPort;
  • ClockPort;
  • SessionControlPort.

WriteBusPort::execute przyjmuje wyłącznie PreparedBusWrite:

  • typ jest publiczny dla implementacji adaptera;
  • pola są prywatne;
  • nie ma publicznego konstruktora;
  • jedyny konstruktor znajduje się w prywatnym kernelu WriteCoordinator;
  • TUI, backup, logger i composition root nie mogą utworzyć tej wartości.

Autorytatywność profilu

  • WriteIntent jest tylko żądaniem operatora.
  • Przed utworzeniem planu Coordinator pobiera aktywny ValidatedDeviceProfile po profile_hash.
  • Access, guards, target raw, write function i read-back policy są pobierane ponownie z profilu.
  • preview_raw z intentu nie jest autorytatywne.

Dwie fazy użytkowe

1. prepare_write(intent) -> PreparedWritePlan

Wykonuje:

  1. process gate;
  2. Connected + Armed + audit Healthy + operation Idle;
  3. zgodność SessionId/fingerprint/profile hash;
  4. trust bieżącego profilu;
  5. odrzucenie ReadOnly/Dangerous/motion/fault reset/raw write;
  6. pobranie świeżej wartości i guards;
  7. rekalkulację engineering → raw;
  8. sprawdzenie zakresu, kroku i reprezentowalności.

Wynikiem jest niezmienny, single-use PreparedWritePlan zawierający:

  • PlanId;
  • session/fingerprint/profile/parameter;
  • context hash;
  • old raw/engineering;
  • requested engineering i recomputed target raw;
  • model potwierdzenia;
  • challenge;
  • expires_at, maksymalnie 15 sekund.

Faza prepare nie wysyła write i nie tworzy PreparedToken audytu urządzenia.

2. confirm_write(plan_id, confirmation)

  • Atomowo konsumuje plan; ponowne użycie jest niemożliwe.
  • Ponownie sprawdza session, fingerprint, profile hash, trust, authorization, audit health i operation Idle.
  • Ponownie pobiera finalną świeżą old value i wszystkie guards po potwierdzeniu operatora.
  • Jakakolwiek zmiana daje PreconditionChanged, zero write i próbę trwałego zapisu rekordu decyzji.
  • Ustawia operation = SingleWrite dopiero przed urządzeniową fazą audytu.
  • Wywołuje AuditPort::prepare_device_write i czeka na trwały sukces.
  • Błąd prepare resetuje operation do Idle, rozbraja i ustawia Degraded.
  • Dopiero po sukcesie tworzy PreparedBusWrite i wywołuje dokładnie jeden write.
  • Wykonuje bounded read-back.
  • Wywołuje finalize_device_write.
  • Kończy operation i aktualizuje session state.

AuditPort — rozdzielone kontrakty

Decyzje przed fizycznym write

record_decision(DecisionAuditRecord) obsługuje:

  • Expired;
  • Cancelled;
  • RejectedByPolicy;
  • ProfileNotTrusted;
  • PreconditionChanged.

Nie wymaga tokenu urządzenia.

Jeżeli samo record_decision nie może zostać trwale wykonane:

  • Coordinator zwraca NotExecuted(AuditUnavailable);
  • nie wysyła write;
  • rozbraja sesję i ustawia audit health Degraded;
  • emituje wyłącznie best-effort diagnostykę o pierwotnej decyzji i błędzie storage;
  • nie próbuje rekurencyjnie zapisać AuditUnavailable i nie udaje istnienia trwałego rekordu.

Fizyczny write

  • prepare_device_write(DeviceWritePreparation) -> PreparedToken — trwały rekord przed write.
  • finalize_device_write(PreparedToken, DeviceWriteOutcome, ReadBackEvidence) — wyłącznie wyniki po rozpoczęciu write.
  • PreparedToken jest single-use i związany z PlanId/RequestId/context hash.

Outcomes

WriteOutcome =
  NotExecuted(DecisionOutcome)
  Executed(DeviceWriteOutcome)

DecisionOutcome:

  • Expired;
  • Cancelled;
  • RejectedByPolicy;
  • ProfileNotTrusted;
  • PreconditionChanged;
  • AuditUnavailable — brak trwałego rekordu, nie dowód jego istnienia.

DeviceWriteOutcome:

  • Verified;
  • DeviceRejected;
  • ReadBackMismatch;
  • OutcomeUnknown;
  • TransportLost;
  • AuditDegraded.

Prywatny kernel i rozszerzalna granica operacji

Prywatny execute_once przyjmuje sealed ExecutionAuthority:

OperationStepAuthority:

#17 później dodaje jedyną produkcyjną metodę WriteCoordinator::authorize_restore_step(&mut RestoreOperationPermit, index), która po pełnej weryfikacji permitu tworzy OperationStepAuthority wewnątrz tego samego crate’a i natychmiast przekazuje je do kernela. Nie wymaga to zmiany publicznego API transportu ani kryteriów zamknięcia #16.

Wykonanie i read-back

  • Dokładnie jeden write.
  • Brak retry write.
  • TimeoutBeforeSend = pewne zero write.
  • Timeout po rozpoczęciu = OutcomeUnknown i disarm.
  • Delayed apply może wykonać 1–3 dodatkowe read-back po settle delay; nigdy kolejny write.
  • Integer/fixed/BCD/enum/bitfield używają exact raw albo accepted raw set z [PLAN 04] Zaprojektować wersjonowany format profilu i pełną walidację #4.
  • Float używa polityki profilu.
  • UI aktualizuje wartość dopiero po pipeline/read-back.

Zakres 1.0 manual write

Dozwolone manualnie:

  • pojedynczy WritableWhenStopped;
  • pojedynczy Commissioning po dodatkowym potwierdzeniu kodu i wartości.

Niedozwolone:

  • Dangerous;
  • raw address/PDU;
  • broadcast;
  • motion;
  • fault reset;
  • arbitralny bulk write;
  • rollback.

Testy

  • Każda bramka i decyzja NotExecuted.
  • Plan expiry, single-use i context hash mismatch.
  • Zmiana old value/guard po prepare, przed confirm.
  • Intent preview/read-back policy nie może zmienić profilu.
  • Brak AuditPort/ProfileTrustPort = zero write.
  • Awaria record_decision zwraca AuditUnavailable, degraduje i nie wykonuje rekurencyjnego audytu.
  • Prepare device audit failure = zero write + operation Idle + Degraded.
  • Success, exception, TimeoutBeforeSend, OutcomeUnknown, disconnect, mismatch.
  • Delayed apply: dodatkowe read, dokładnie jeden write.
  • Finalize failure = AuditDegraded.
  • Compile fixture: PreparedBusWrite i OperationStepAuthority niekonstruowalne poza dozwolonym modułem.
  • Testowy wariant OperationStep przechodzi przez ten sam kernel i respektuje exact target/context, bez implementacji restore.
  • Composition root po [PLAN 16] Zbudować dwuetapowy core prepare/confirm zapisu #16 nadal nie udostępnia write.

Kryteria akceptacji

Zależności

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions