Skip to content

fix: validate plan backup copy schedule and backup window, guard against null backupCopy - #1

Merged
protego-maxima-fianto-duri merged 1 commit into
synology-apm:mainfrom
davidleitw:fix/plan-validation-and-null-backupcopy
Jul 23, 2026
Merged

fix: validate plan backup copy schedule and backup window, guard against null backupCopy#1
protego-maxima-fianto-duri merged 1 commit into
synology-apm:mainfrom
davidleitw:fix/plan-validation-and-null-backupcopy

Conversation

@davidleitw

Copy link
Copy Markdown
Contributor

Summary

While looking through the protection-plan write path, I came across a few cases
where an unusual or malformed plan configuration is accepted quietly and only
shows up as unexpected behavior later on. This PR adds validation for two of
them and fixes a crash I ran into while listing plans against a live server.

I've tried to keep the changes small and self-contained, and I may well be
missing context on why some of these behave the way they do — so please feel
free to push back on anything, and I'm happy to adjust to match the project's
conventions.

All three were checked against a live APM server, and make test passes locally
(2999 passed, ruff + mypy clean, coverage 98.76%).

What's changed

1. WEEKLY Backup Copy schedule with no weekdays

ProtectionSchedule's docstring notes that a WEEKLY schedule needs at least one
weekday, and the main plan schedule already checks this in
_validate_plan_schedule_and_retention. The same rule didn't seem to be applied
to BackupCopyConfig.schedule, so a WEEKLY copy schedule with empty weekdays was
accepted and sent as runWeekday: []. In my testing the server stored it as-is
and the copy never ran, with no error at creation time.

I added a small _validate_backup_copy() helper called from both
MachinePlanCreateRequest.__post_init__ and M365PlanCreateRequest.__post_init__.
AFTER_BACKUP stays valid for Backup Copy, so only the WEEKLY-weekday rule applies.
Both Raises: docstrings and the two MCP tool descriptions are updated to match.

2. Overnight / out-of-range backup window hours

A window like mon:20-8 (intended as 8pm–8am) expands to range(20, 9), which
is empty, so that weekday quietly becomes fully blocked. Out-of-range hours such
as mon:30-40 are similarly dropped. This PR rejects both with a ValueError
(the overnight message suggests splitting into two ranges, e.g. 20-23,0-8)
instead of silently producing a no-backup window.

The check runs at construction time (MachineBackupWindow.__post_init__) and at
the MCP parse layer, with the request builder kept as a last-resort guard. It
only applies when enabled=True, to preserve the existing "allowed_hours is
ignored when disabled" behavior. If you'd rather handle overnight windows
differently (e.g. accept and split them automatically), I'm glad to revisit —
rejecting felt like the safer default, but I wasn't sure of the intended
semantics.

3. Null backupCopy when parsing plans

apm.plans.list() / get() raised AttributeError when a plan's
spec.backupCopy was JSON null (rather than absent), because
.get("backupCopy", {}) returns None in that case. I hit this on a live server
that happened to have such plans. Both sites now use .get("backupCopy") or {},
treating null the same as "no backup copy". This should leave the existing
{"enabled": false} behavior unchanged.

Testing

  • 13 new unit tests covering the three changes; each was checked to fail without
    its corresponding fix.
  • Verified against a live server: the two validations now reject the malformed
    input before it reaches the server, a valid plan (WEEKLY copy with weekdays plus
    a normal window) is still accepted, and plans.list() succeeds against plans
    with a null backupCopy.
  • make test passes end to end.

Scope

I kept this focused on the three items above. A couple of related things I left
out on purpose to keep the PR small — happy to follow up separately if they'd be
useful:

  • The MANUAL / HOURLY Backup Copy frequencies fall through to a daily encoding
    when used directly via the SDK (the MCP layer already restricts these via a
    Literal).
  • A similar null-collapse pattern might exist for other optional nested fields
    (e.g. retention), but I only actually observed backupCopy as null, so I
    didn't want to touch paths I couldn't reproduce.

Thanks very much for taking a look — please let me know if you'd prefer any of
this split up, reshaped, or dropped.

… backupCopy

- Reject WEEKLY Backup Copy schedules with no weekdays at request construction
- Reject overnight/out-of-range backup window hours instead of silently blocking
- Survive plans whose spec.backupCopy is JSON null in list()/get()
@protego-maxima-fianto-duri
protego-maxima-fianto-duri merged commit 484692f into synology-apm:main Jul 23, 2026
4 checks passed
@davidleitw
davidleitw deleted the fix/plan-validation-and-null-backupcopy branch July 23, 2026 11:40
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.

2 participants