{
  "part": 9,
  "title": "Base-selection fallback when a resume point expires",
  "reset": "standard",
  "_comment": "Steps are declared in a different ORDER than TESTING.md's own numbering: TESTING.md itself notes that a straight 9.1/9.2/9.3/9.4 run does NOT leave Part 10's required state, and a human tester must re-run 9.3 before continuing. Since catalogue execution order is array order (not id order), 9.4 is placed before 9.3 here so 9.3 genuinely IS the last thing that runs, leaving its 'no common base' state exactly as Part 10 needs it - no re-run required.",
  "steps": [
    { "id": "9.1", "title": "Inferred fallback, the common case",
      "_comment": "Snapshot names embed a real date (via the now_suffix primitive), matching TESTING.md's own $(date +%Y-%m-%d_%H.%M.%S) convention - ZFS_Utils::parseSnapshotDateTime/findCommonBaseSnapshot only ever consider a snapshot as a base-selection candidate if its name embeds a YYYY-MM-DD date; a bare literal like 'S1_9_1' can never trigger the confirmed/inferred fallback this Part exists to test. sleep 1 between each guarantees distinct second-resolution suffixes.",
      "actions": [
        { "do": "now_suffix", "store_as": "SUF1_91" },
        { "do": "zfs_snapshot", "dataset": "{SRC}/ds1@S1_$SUF1_91" },
        { "do": "run_sneakernet", "servername": "test-source", "verbosity": 1, "expect_exit": 0 },
        { "do": "run_sneakernet", "servername": "test-target", "verbosity": 1, "expect_exit": 0 },
        { "do": "sleep", "seconds": 1 },
        { "do": "now_suffix", "store_as": "SUF2_91" },
        { "do": "zfs_snapshot", "dataset": "{SRC}/ds1@S2_$SUF2_91" },
        { "do": "empty_dir", "path": "{TRANSPORT}" },
        { "do": "run_sneakernet", "servername": "test-source", "verbosity": 1, "expect_exit": 0 },
        { "do": "run_sneakernet", "servername": "test-target", "verbosity": 1, "expect_exit": 0 },
        { "do": "sleep", "seconds": 1 },
        { "do": "now_suffix", "store_as": "SUF3_91" },
        { "do": "zfs_snapshot", "dataset": "{SRC}/ds1@S3_$SUF3_91" },
        { "do": "zfs_destroy", "dataset": "{SRC}/ds1@S2_$SUF2_91", "recursive": 0, "missing_ok": 0 },
        { "do": "empty_dir", "path": "{TRANSPORT}" },
        { "do": "run_sneakernet", "servername": "test-source", "verbosity": 4, "log": "part9_1", "expect_exit": 0,
          "capture": { "name": "LOG91" } },
        { "do": "run_sneakernet", "servername": "test-target", "verbosity": 1, "log": "part9_1_target", "expect_exit": 0,
          "capture": { "name": "LOG91_TARGET" } },
        { "do": "zfs_list_snapshots", "dataset": "{DST}/ds1", "capture": { "name": "DST_DS1_91", "reduce": "join" } }
      ],
      "assertions": [
        { "type": "regex_present", "var": "LOG91", "pattern": "(?i)recorded resume point '\\S+' is gone", "message": "log explicitly says the recorded resume point no longer exists on the source" },
        { "type": "regex_present", "var": "LOG91", "pattern": "(?i)inferred substitute base", "message": "log says it inferred a substitute base" },
        { "type": "regex_present", "var": "LOG91", "pattern": "zfs send[^\\n]*-[iI][^\\n]*ds1|zfs send[^\\n]*ds1[^\\n]*-[iI]", "message": "the printed zfs send command is incremental (-i/-I) from S1, not a bare full send" },
        { "type": "regex_present", "var": "DST_DS1_91", "pattern": "ds1\\@S1_$SUF1_91", "message": "target has S1" },
        { "type": "regex_absent", "var": "DST_DS1_91", "pattern": "ds1\\@S2_$SUF2_91", "message": "S2 does NOT survive - zfs receive -F rolls the target back to the inferred base (S1) before applying the incremental to S3, destroying S2 (see TESTING_automation.md's documented finding; this is often the correct outcome of retention/TTL pruning, not a bug - it just needs to be visible, which the next assertion checks for)" },
        { "type": "regex_present", "var": "DST_DS1_91", "pattern": "ds1\\@S3_$SUF3_91", "message": "target now has S3 (received via the inferred incremental)" },
        { "type": "regex_present", "var": "LOG91_TARGET", "pattern": "original snapshot no longer exists on the target", "message": "the target's own comparison report explicitly notes that S2 was rolled back, rather than silently reporting it as '1 added'" }
      ]
    },
    { "id": "9.2-setup", "title": "Deliver S1 and S2 normally, then force S1 to be the only viable base (setup for 9.2)", "depends_on": ["9.1"],
      "_comment": "No config.mutations here, deliberately: source.targetSnapshotList must NOT be active yet for these two ordinary deliveries - it used to be set once for the whole (unsplit) step, which meant these two 'just deliver normally' run_sneakernet calls incorrectly read a stale, leftover inventory file from a previous session instead of using the normal status-file path (confirmed via audit.jsonl: both showed 'Using operator-supplied target snapshot list ... instead of the status file'). Split into its own step so the mutation in 9.2 itself only ever applies to the one confirmed-check run it's meant for.",
      "actions": [
        { "do": "zfs_destroy", "dataset": "{DST}/ds1", "recursive": 1, "missing_ok": 1 },
        { "do": "rm_matching", "dir": "{SN}", "pattern": "^sneakernet_target\\.status(\\.[0-9_.-]+)?$" },
        { "do": "empty_dir", "path": "{TRANSPORT}" },
        { "do": "now_suffix", "store_as": "SUF1_92" },
        { "do": "zfs_snapshot", "dataset": "{SRC}/ds1@S1_$SUF1_92" },
        { "do": "run_sneakernet", "servername": "test-source", "verbosity": 1, "expect_exit": 0 },
        { "do": "run_sneakernet", "servername": "test-target", "verbosity": 1, "expect_exit": 0 },
        { "do": "sleep", "seconds": 1 },
        { "do": "now_suffix", "store_as": "SUF2_92" },
        { "do": "zfs_snapshot", "dataset": "{SRC}/ds1@S2_$SUF2_92" },
        { "do": "empty_dir", "path": "{TRANSPORT}" },
        { "do": "run_sneakernet", "servername": "test-source", "verbosity": 1, "expect_exit": 0 },
        { "do": "run_sneakernet", "servername": "test-target", "verbosity": 1, "expect_exit": 0 },
        { "do": "sleep", "seconds": 1 },
        { "do": "now_suffix", "store_as": "SUF3_92" },
        { "do": "zfs_snapshot", "dataset": "{SRC}/ds1@S3_$SUF3_92" },
        { "do": "zfs_destroy", "dataset": "{SRC}/ds1@S2_$SUF2_92", "recursive": 0, "missing_ok": 0 },
        { "do": "zfs_list_snapshots", "dataset": "{DST}/ds1", "capture": { "name": "TARGET_INVENTORY_92", "reduce": "lines" } },
        { "do": "write_file", "path": "{TMP}/target_inventory_9_2.txt", "content": "$TARGET_INVENTORY_92\n" }
      ]
    },
    { "id": "9.2", "title": "Confirmed fallback via a real target inventory", "depends_on": ["9.2-setup"],
      "config": { "mutations": [ { "op": "set", "path": "source.targetSnapshotList", "value": "{TMP}/target_inventory_9_2.txt" } ], "restore": "none" },
      "actions": [
        { "do": "empty_dir", "path": "{TRANSPORT}" },
        { "do": "run_sneakernet", "servername": "test-source", "verbosity": 4, "log": "part9_2", "expect_exit": 0,
          "capture": { "name": "LOG92" } },
        { "do": "run_sneakernet", "servername": "test-target", "verbosity": 1, "expect_exit": 0 }
      ],
      "assertions": [
        { "type": "regex_present", "var": "LOG92", "pattern": "(?i)Using operator-supplied target snapshot list", "message": "log shows the supplied inventory file being used" },
        { "type": "regex_present", "var": "LOG92", "pattern": "(?i)confirmed present in the recorded target snapshot state", "message": "the base resolves via an exact/confirmed match rather than a guess" }
      ]
    },
    { "id": "9.2-cleanup", "title": "Restore targetSnapshotList: ''", "depends_on": ["9.2"],
      "config": { "restore": "none", "mutations": [
        { "op": "set", "path": "source.targetSnapshotList", "value": "", "style": "single" }
      ] }
    },
    { "id": "9.4", "title": "Source dataset destroyed and NOT recreated", "depends_on": ["9.2-cleanup"],
      "actions": [
        { "do": "zfs_destroy", "dataset": "{DST}/ds1", "recursive": 1, "missing_ok": 1 },
        { "do": "rm_matching", "dir": "{SN}", "pattern": "^sneakernet_target\\.status(\\.[0-9_.-]+)?$" },
        { "do": "empty_dir", "path": "{TRANSPORT}" },
        { "do": "zfs_snapshot", "dataset": "{SRC}/ds1@S1_9_4" },
        { "do": "run_sneakernet", "servername": "test-source", "verbosity": 1, "expect_exit": 0 },
        { "do": "run_sneakernet", "servername": "test-target", "verbosity": 1, "expect_exit": 0 },
        { "do": "zfs_destroy", "dataset": "{SRC}/ds1", "recursive": 1, "missing_ok": 1 },
        { "do": "empty_dir", "path": "{TRANSPORT}" },
        { "do": "run_sneakernet", "servername": "test-source", "verbosity": 2, "log": "part9_4", "expect_exit": 0,
          "capture": { "name": "LOG94" } },
        { "do": "zfs_create", "dataset": "{SRC}/ds1" }
      ],
      "assertions": [
        { "type": "regex_present", "var": "LOG94", "pattern": "storage/testing/src/ds1.*does not exist - it may have been destroyed", "message": "log names ds1's full source dataset path and states it does not exist" }
      ]
    },
    { "id": "9.3", "title": "No base at all (a recreated dataset)", "depends_on": ["9.4"],
      "actions": [
        { "do": "zfs_destroy", "dataset": "{DST}/ds1", "recursive": 1, "missing_ok": 1 },
        { "do": "rm_matching", "dir": "{SN}", "pattern": "^sneakernet_target\\.status(\\.[0-9_.-]+)?$" },
        { "do": "empty_dir", "path": "{TRANSPORT}" },
        { "do": "zfs_snapshot", "dataset": "{SRC}/ds1@S1_9_3" },
        { "do": "run_sneakernet", "servername": "test-source", "verbosity": 1, "expect_exit": 0 },
        { "do": "run_sneakernet", "servername": "test-target", "verbosity": 1, "expect_exit": 0 },
        { "do": "zfs_destroy", "dataset": "{SRC}/ds1", "recursive": 1, "missing_ok": 1 },
        { "do": "zfs_create", "dataset": "{SRC}/ds1" },
        { "do": "zfs_snapshot", "dataset": "{SRC}/ds1@S2_9_3" },
        { "do": "empty_dir", "path": "{TRANSPORT}" },
        { "do": "run_sneakernet", "servername": "test-source", "verbosity": 4, "log": "part9_3", "expect_exit": 0,
          "capture": { "name": "LOG93" } }
      ],
      "assertions": [
        { "type": "regex_present", "var": "LOG93", "pattern": "(?i)no common base", "message": "log clearly states no common base snapshot could be found for ds1" }
      ]
    }
  ]
}
