Skip to content

refactor(backend): provider-scoped rollout Wave 2 (remediation)#29

Merged
creativeux merged 2 commits into
mainfrom
feat/provider-scoped-wave2
Jul 1, 2026
Merged

refactor(backend): provider-scoped rollout Wave 2 (remediation)#29
creativeux merged 2 commits into
mainfrom
feat/provider-scoped-wave2

Conversation

@creativeux

Copy link
Copy Markdown
Owner

What

Wave 2 of the provider-scoped rollout: converts remediation — the first vertical with a real write, exercising the write/command pattern documented in the standard. Behavior-preserving (no /api change).

Spec: specs/2026-06-29_feature_providerScopedRollout/spec-wave2.md (passed Architect+QA persona review and the standards gate).

Changes

  • VoipMsRemediationRepository owns the client and exposes ensure_max_expiry(sub, target) — an idempotent set-then-verify command. The "already at target → don't write" check lives in the command, which preserves the exact provider call counts. get_max_expiry() handles the read. Provider failures → ProviderLookupError; unknown sub-account → ValueError.
  • MaxExpiryOutcome domain model; RemediationService maps it → RemediationResult (already-set / updated / verify-failed) and is provider-free for the registration-interval concern.
  • RemediationRepository ABC in protocols.py.
  • Router: registration endpoints build the repo-backed service and map ProviderLookupError → identical 502/504; the sync endpoint injects the PC service.

The one design call: sync_parental_controls

sync_parental_controls delegates to ParentalControlsService, a Wave 3 giant still coupled to the client. To make remediation provider-free now, it's injected (service→service composition) rather than constructed from a client.

Honest scoping: that injected service is unconverted and raises raw VoipMsError/httpx, so the sync path retains a documented transitional catch of those types — catching only ProviderLookupError would have silently changed behavior. The registration-interval concern (the actual conversion) is fully provider-free; the residual sync-path import is documented in-code and in the standard's migration note, and clears when parental controls converts in Wave 3.

Verification

  • Registration-interval concern provider-free; sync-path exception handling is the one documented transitional exception.
  • Repo tests assert the idempotency guarantee (set_sub_account not called when already at target), verify-mismatch/empty → not verified, provider-error translation.
  • Service tests (fake repo + stub pc_service) cover the three RemediationResult outcomes and — newly — all sync_parental_controls paths.
  • Full suite 707 passed (+18 net); ruff check + ruff format --check clean.

Next

Wave 3 = parental controls + wizard — which also clears the remediation sync-path transitional exception.

🤖 Generated with Claude Code

creativeux and others added 2 commits July 1, 2026 13:25
Convert the remediation vertical — the first with a real write — per
specs/2026-06-29_feature_providerScopedRollout/spec-wave2.md. Behavior-preserving.

- VoipMsRemediationRepository owns the client and exposes ensure_max_expiry(),
  an idempotent set-then-verify write command (no write when already at target,
  which preserves provider call counts). get_max_expiry() for the read.
  Provider failures → ProviderLookupError; unknown sub-account → ValueError.
- MaxExpiryOutcome domain model; RemediationService maps it → RemediationResult
  (already-set / updated / verify-failed) and is provider-free for the
  registration-interval concern.
- sync_parental_controls delegates to an INJECTED ParentalControlsService
  (service→service composition) so remediation doesn't construct a client. That
  service is unconverted (Wave 3) and raises raw VoIP.ms errors, so the sync path
  retains a documented transitional catch of VoipMsError/httpx — it clears when
  parental controls converts.
- Router: registration endpoints build the repo-backed service and map
  ProviderLookupError → identical 502/504; sync endpoint injects the PC service.
- Tests rewritten (fake repo + stub pc_service); added sync_parental_controls
  coverage. Standard migration note updated (remediation done; exception noted).

Full suite 707 passed; ruff check + format clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address PR #29 review should-fix #1: the empty-accounts read-back path
collapsed two distinct RemediationResult.detail strings into one, emitting
"...max_expiry is still  after update" (double space) instead of the original
"...could not retrieve sub-account after update" — an observable /api change.

- MaxExpiryOutcome.actual is now str | None (None = read-back returned no
  account); the service maps None → "could not retrieve sub-account after
  update", a value mismatch → "max_expiry is still {actual} after update".
- Restore the service-level empty-verify detail assertion (review #2) and fix
  the repo test to assert actual is None.
- Nit: restore exc_info=True on the router's unreachable/upstream remediation
  log lines (dropped stack context).

Full suite 708 passed; ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@creativeux creativeux self-assigned this Jul 1, 2026
@creativeux creativeux merged commit 72ba8f9 into main Jul 1, 2026
2 checks passed
@creativeux creativeux deleted the feat/provider-scoped-wave2 branch July 1, 2026 19:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant