ADR-003: Model zatwierdzania i Human-in-the-loop

Decyzja

Nie zatwierdzać ręcznie każdego deployu. Podział klas operacji:

KlasaPrzykładTryb
Read-onlyDNS query, methods, healthautomatyczny
Reversible mutatepublikacja kolejnego releaseautomatyczny po obowiązkowym dry-run + grant
Boundary changenowa domena, DNS, utworzenie credentialjednorazowy HITL
Governance changenowy intent pack, AQL allow, politykaobowiązkowy review człowieka

Po bootstrapie domeny/credentials/polityki publikacja docs = zero-touch (w środowisku z zielonym preflightem).

Bramki apply (fail-closed)

  1. Master kill switch: AUTONOMY_MUTATIONS_ENABLED — jawne 0 blokuje; 1 otwiera master.
  2. Domain kill switch: PLESK_SYNC_APPLY=1 dla syncu Plesk (legacy ops gate).
  3. Signed apply grant związany z bindingami poniżej.
  4. Immutable manifest z dry-run — apply weryfikuje plan_hash, nie przelicza swobodnie listy plików.

Founder bypass (SUBACTOR_ADMIN_TOKEN) nie zastępuje grantu w modelu produkcyjnym.


Specyfikacja apply grant (krytyczna)

Signer / issuer / audience

PoleWartość
Signer / issuerControl service (hr-control) — endpoint POST /api/apply-grants
Wymagany scopeplans:approve (founder/admin / approver)
Audience (verifier)Bridge planner + urirun-connector-plesk (oraz przyszłe mutate connectors)
AlgorytmHMAC-SHA256 (alg: "HS256"), signature base64url (bez paddingu =)
Canonical payloadJSON object z ustałą kolejnością kluczy (nie alfabetyczną): run_id, actor, intent_pack, plan_hash, artifact_sha256, target, expires_at, risk_class — wartości string, JSON.stringify bez spacji (separators (',', ':') w Pythonie)

Klucz

PreferowanyAPPLY_GRANT_HMAC_SECRET (vault lub env noda; nigdy w ticketach/logach)
Fallback labTOKEN_PEPPER (tylko gdy primary unset; ten sam sekret na control + verifier)
Brak sekretufail-closed: apply_grant_secret_missing — brak mutacji
RotacjaWprowadzić nowy sekret jako APPLY_GRANT_HMAC_SECRET_NEXT, dual-verify (current \\next) przez ≤ TTL max, potem drop old; audit apply_grant.key_rotated
Verifier unavailableBrak sekretu lub błąd crypto → deny (nie allow-open)

Claims (binding)

Wymagane: run_id, actor, intent_pack, plan_hash, artifact_sha256, target, expires_at, risk_class.

Opcjonalne w ADR (pełny replay = PR5c): jti, iat. PR5b wydaje jti w tokenie; store użytych jti pozostaje PR5c.

risk_classread_only \| reversible \| boundary \| governance. Mutate sync = zwykle reversible.

TTL / clock skew

Default TTL15 minut
Max TTL60 minut (control odrzuca dłuższe)
Clock skewverifier: expires_at ważny jeśli now <= expires_at + 60s (skew); reject jeśli expires_at < now - 60s już przy issue
Expiredapply_grant_expired

Replay (PR5c) — done

Immutable manifest (PR5a)

Deny codes (kontrakt)

plesk_sync_apply_required \| autonomy_mutations_disabled \| apply_grant_required \| apply_grant_secret_missing \| apply_grant_signature_invalid \| apply_grant_expired \| apply_grant_plan_hash_mismatch \| apply_grant_target_mismatch \| apply_grant_artifact_mismatch \| apply_grant_replay \| plan_hash_mismatch

Sekrety nigdy w grant body, ticketach, recipe, NL logs.

Konsekwencje