Linux engineer examining systemd timer logs and UTC timestamps to verify a single catch-up activation after missed calendar triggers

A Persistent systemd Timer Does Not Replay Every Missed Run

Mon, Oct 19, 2026

A successful systemctl --user start command and a recent timer timestamp can look like recovery evidence. Neither answers the service owner's harder question: did every scheduled run missed during inactivity actually happen? Before accepting that conclusion, distinguish the calendar instants that passed, the service invocations that began, the timer's retained trigger history, and the work that completed successfully.

This playbook tests that distinction with four explicit UTC instants and three disposable user-manager profiles. Each profile should run once, remain stopped across three further instants, and restart only after the entire schedule is in the past. The proposed comparison asks whether persistence supplies an additional opportunity to run, and whether a deliberately failing service changes that interpretation.

The expected totals are one invocation without persistence and two with persistence, including two for the failing profile. Those numbers are authored assertions. The supplied commissioning environment had no usable systemd user bus, so the services, waiting sequence, and activation assertions were NOT RUN. No new VM execution is represented here.

The decision is deliberately limited. Accept a coalesced trigger only when the workload owner accepts that scheduling contract. Require a different contract when every missed period needs its own execution. Hold when the evidence cannot distinguish those cases. The laboratory uses an ordinary account on an owned disposable Linux VM; it does not reboot a host, alter clocks, modify system-wide services, or replay historical business work.

Define the difference between a missed instant and missed work

The systemd v257 timer manual source describes Persistent=true as storing the time of the last service trigger on disk. On timer activation, elapsed calendar eligibility during inactivity can cause a catch-up trigger. This applies to OnCalendar= timers and remains subject to configured delay. The documented condition asks whether the service would have been triggered at least once; it does not describe a queue containing one entry for every missed instant.

Use four separate evidence categories:

Category

Question

Evidence in this laboratory

Calendar eligibility

Which configured instants passed?

Independent four-instant schedule

Recorder invocation

Did the worker reach its logging operation?

JSONL row with a distinct activation ID

Trigger history

What last-trigger state does the timer expose?

Retained timer properties before and after inactivity

Service outcome

How did the invoked process terminate?

Service result and exit-state snapshots

Actual business completion would need an additional workload-specific test. The recorder deliberately has no invoices, backup contents, external API effects, or historical period inputs. Its successful exit cannot certify any of those things.

Define the acceptance unit before counting. If the requirement is an opportunity to refresh current state after downtime, one later invocation might be useful. If three missed periods require three separately completed outputs, the proposed catch-up count cannot satisfy that requirement. These are example classifications to review with the owner, not approvals for unnamed workloads.

Keep systemd readiness as a separate contract: deciding when dependents may proceed answers a different operational question. Here, the boundary is whether a stopped calendar timer later requests service activity. Every subsequent recovery claim must identify which evidence category supports it.

Identify the manager, timer and persistent state

Every manager command in this protocol uses --user. Confirm that the account owns the disposable VM session and can contact its existing user manager. Do not use elevated privileges or switch to the system manager to make a failed preflight appear successful.

Prepare a fresh outer capture directory in Bash before running the driver. This supplemental capture protocol is also NOT RUN here:

probe_capture=$(mktemp -d "$PWD/timer-capture.XXXXXXXX") || exit 1
printf '%s\n' "$probe_capture"
date -u +%Y-%m-%dT%H:%M:%SZ >"$probe_capture/preflight.started"
systemctl --user show --property=Version --all \
  >"$probe_capture/manager.txt" 2>"$probe_capture/manager.stderr"
probe_manager_rc=$?
printf '%s\n' "$probe_manager_rc" >"$probe_capture/manager.exit"
test "$probe_manager_rc" -eq 0 || exit "$probe_manager_rc"
test -n "$(sed -n 's/^Version=//p' "$probe_capture/manager.txt")" || exit 1

The v257 systemctl manual source documents that show without a unit argument queries the manager. Preserve its nonempty Version alongside the driver's systemctl --version output. The latter identifies the client binary; it does not establish which build the running manager uses.

Record all six generated unit names, their shared random token, and the three explicit Unit= relationships. Retain effective OnCalendar, Persistent, accuracy, and delay properties. The source targets an ordinary user configuration directory; verify the account's configuration and executable paths are suitable before launch.

An installed systemctl is not a manager test

The supplied commissioning evidence reported systemd 257 (257.9-1~deb13u1) from the available binary, but no usable systemd user bus. Only Python compilation occurred. That establishes neither a running manager nor catch-up behavior. A bus failure remains HOLD, even if the binary prints a plausible version.

The driver's bus preflight occurs before its evidence directory exists. Retain the outer stderr and exit status even when there is no evidence.json to inspect. An absent directory following a refused or unavailable environment is an incomplete experiment, never an empty passing run.

The documentation baseline is the official v257 tag. It is not a newest-release assertion. Record a different installed build honestly and investigate any disagreement. For comparison, business-period CronJob recovery concerns application outcomes; its ledger cannot substitute for missing user-manager evidence here.

Build a disposable finite-calendar experiment

Save the complete source as systemd_timer_probe.py. It refuses root and tests the user bus before creating units. Use ordinary Python with PYTHONOPTIMIZE unset and without -O or -OO; essential gates use assert.

The independent input manifest is:

Instant

Approximate offset from origin

Required timer condition

T1

+30 seconds

Running; one baseline invocation

T2

+75 seconds

Stopped

T3

+120 seconds

Stopped

T4

+165 seconds

Stopped

Exact values are generated UTC timestamps, not these rounded offsets. Explicit dates and time zones follow the v257 systemd.time manual source. The source SHA-256, including its final newline, is 940b35c45ad1ba939dd7f4d5cf5829eb08b165f4475f001a4026993a3f288184. The listing is unchanged; its assertions remain unexecuted service-test expectations.

"""Disposable Linux user-manager experiment; NOT executed in the commissioning run.
Runs for about three minutes. Does not alter the clock, reboot, or need root.
"""
from __future__ import annotations
import json, os, platform, subprocess, sys, tempfile, time, uuid
from datetime import datetime, timezone
from pathlib import Path
def main() -> None:
    if platform.system() != 'Linux' or os.geteuid() == 0:
        raise SystemExit('Use an ordinary user on a disposable Linux VM')
    subprocess.run(['systemctl', '--user', 'show-environment'], check=True, stdout=subprocess.DEVNULL, timeout=10)
    token = uuid.uuid4().hex[:10]
    root = Path(tempfile.mkdtemp(prefix='refonte-timer-'))
    units = Path(os.environ.get('XDG_CONFIG_HOME', str(Path.home()/'.config')))/'systemd/user'
    units.mkdir(parents=True, exist_ok=True)
    origin_wall, origin_mono = time.time(), time.monotonic()
    base = int(origin_wall) + 30
    deadlines = [base + offset for offset in (0, 45, 90, 135)]
    schedules = [datetime.fromtimestamp(t, timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC') for t in deadlines]
    evidence = {'python': sys.version, 'platform': platform.platform(), 'directory': str(root), 'schedule_utc': schedules, 'status': 'INCOMPLETE', 'commands': [], 'cleanup_errors': []}
    created, timers, services, profiles = [], [], [], []
    def command(*args: str, check: bool = True) -> subprocess.CompletedProcess:
        start = datetime.now(timezone.utc).isoformat()
        result = subprocess.run(args, text=True, capture_output=True, timeout=15)
        evidence['commands'].append({'argv': list(args), 'started': start, 'returncode': result.returncode, 'stdout': result.stdout, 'stderr': result.stderr})
        if check and result.returncode:
            raise RuntimeError(f'HOLD: command failed: {args}')
        return result
    def clock_guard() -> None:
        if abs((time.time()-origin_wall) - (time.monotonic()-origin_mono)) > 2:
            raise RuntimeError('HOLD: wall/monotonic clock drift exceeded lab tolerance')
    def lines(path: Path) -> list[dict]:
        return [json.loads(x) for x in path.read_text().splitlines()] if path.exists() else []
    def states() -> dict:
        result = {}
        for p in profiles:
            text = command('systemctl', '--user', 'show', p['service'], '--property=ActiveState,SubState,Result,ExecMainStatus').stdout
            result[p['id']] = dict(line.split('=', 1) for line in text.splitlines() if '=' in line)
        return result
    def wait_for(predicate, seconds: float) -> None:
        limit = time.monotonic()+seconds
        while not predicate():
            clock_guard()
            if time.monotonic() >= limit:
                raise RuntimeError('HOLD: laboratory watchdog expired')
            time.sleep(0.2)
    def quoted(path: Path) -> str:
        # systemd quoting and literal percent handling; no shell interpretation.
        return '"'+str(path).replace('\\', '\\\\').replace('"', '\\"').replace('%', '%%')+'"'
    worker = root/'record.py'
    worker.write_text('''import json, os, sys, time, uuid
from datetime import datetime, timezone
from pathlib import Path
path, fail = Path(sys.argv[1]), int(sys.argv[2])
row = {"activation_id":uuid.uuid4().hex,"pid":os.getpid(),
       "utc":datetime.now(timezone.utc).isoformat(),"epoch":time.time(),"requested_exit":fail}
with path.open("a", encoding="utf-8") as stream:
    stream.write(json.dumps(row)+"\\n"); stream.flush(); os.fsync(stream.fileno())
raise SystemExit(fail)
''', encoding='utf-8')
    try:
        evidence['systemd_version'] = command('systemctl', '--version').stdout
        for label, persistent, fail in [('off', False, 0), ('on', True, 0), ('on-fail', True, 1)]:
            name = f'refonte-persistent-{token}-{label}'
            service, timer, log = name+'.service', name+'.timer', root/(label+'.jsonl')
            service_text = ('[Unit]\nDescription=Disposable Refonte timer probe\n' '[Service]\nType=oneshot\nRemainAfterExit=no\n' f'ExecStart={quoted(Path(sys.executable))} {quoted(worker)} {quoted(log)} {fail}\n')
            timer_text = ('[Unit]\nDescription=Disposable Refonte finite calendar\n[Timer]\n' + ''.join(f'OnCalendar={x}\n' for x in schedules) + f'Unit={service}\nPersistent={str(persistent).lower()}\n' + 'AccuracySec=1ms\nRandomizedDelaySec=0\n')
            for filename, text in [(service, service_text), (timer, timer_text)]:
                destination = units/filename
                with destination.open('x', encoding='utf-8') as stream:
                    stream.write(text)
                created.append(destination)
            profiles.append({'id': label, 'persistent': persistent, 'fail': fail, 'service': service, 'timer': timer, 'log': str(log)})
            timers.append(timer); services.append(service)
        evidence['profiles'] = profiles
        command('systemctl', '--user', 'daemon-reload')
        if time.time() > deadlines[0]-5:
            raise RuntimeError('HOLD: setup consumed the first-schedule safety margin')
        command('systemctl', '--user', 'start', *timers)
        wait_for(lambda: all(len(lines(Path(p['log']))) == 1 for p in profiles), 50)
        wait_for(lambda: all(s.get('ActiveState') in ('inactive','failed') for s in states().values()), 8)
        if time.time() > deadlines[1]-5:
            raise RuntimeError('HOLD: cannot stop before the second scheduled instant')
        evidence['first_service_state'] = states()
        assert evidence['first_service_state']['on-fail']['ExecMainStatus'] == '1'
        command('systemctl', '--user', 'show', *timers)
        command('systemctl', '--user', 'stop', *timers)
        evidence['stopped_at'] = datetime.now(timezone.utc).isoformat()
        while time.time() < deadlines[-1]+5:
            clock_guard(); time.sleep(0.2)
        assert all(len(lines(Path(p['log']))) == 1 for p in profiles)
        command('systemctl', '--user', 'start', *timers)
        evidence['restarted_at'] = datetime.now(timezone.utc).isoformat()
        wait_for(lambda: all(len(lines(Path(p['log']))) == (2 if p['persistent'] else 1) for p in profiles), 15)
        wait_for(lambda: all(s.get('ActiveState') in ('inactive','failed') for s in states().values()), 8)
        time.sleep(3)  # A bounded observation window, not a scheduling-latency guarantee.
        evidence['final_service_state'] = states()
        for label in ('off', 'on'):
            assert evidence['final_service_state'][label]['Result'] == 'success'
        assert evidence['final_service_state']['on-fail']['ExecMainStatus'] == '1'
        assert evidence['final_service_state']['on-fail']['Result'] == 'exit-code'
        for p in profiles:
            data = lines(Path(p['log']))
            assert len(data) == (2 if p['persistent'] else 1), (p['id'], data)
            assert len({r['activation_id'] for r in data}) == len(data)
            p['activations'] = data
        command('systemctl', '--user', 'show', timers, services)
        evidence['status'] = 'PASS_3_PROFILES_FINITE_CALENDAR'
    except Exception as error:
        evidence.update(status='HOLD', error=f'{type(error).__name__}: {error}')
        raise
    finally:
        persistent_timers = [p['timer'] for p in profiles if p['persistent']]
        cleanup_commands = [('systemctl','--user','stop',*timers,*services)]
        if persistent_timers:
            cleanup_commands.append(('systemctl','--user','clean','--what=state',*persistent_timers))
        for args in cleanup_commands:
            if timers:
                try:
                    result = command(*args, check=False)
                    if result.returncode:
                        evidence['cleanup_errors'].append(result.stderr)
                except Exception as error:
                    evidence['cleanup_errors'].append(str(error))
        for path in created:
            try:
                path.unlink()
            except OSError as error:
                evidence['cleanup_errors'].append(str(error))
        try:
            command('systemctl','--user','daemon-reload')
        except Exception as error:
            evidence['cleanup_errors'].append(str(error))
        if evidence['cleanup_errors']:
            evidence['status_before_cleanup'] = evidence['status']
            evidence['status'] = 'HOLD_CLEANUP'
        (root/'evidence.json').write_text(json.dumps(evidence, indent=2)+'\n', encoding='utf-8')
        print('Evidence retained at', root)
    if evidence['cleanup_errors']:
        raise RuntimeError('HOLD: cleanup incomplete; inspect the retained evidence')
if __name__ == '__main__':
    main()
# Run from the directory containing that source, retaining the real process status.
(
  set +e
  unset PYTHONOPTIMIZE
  sha256sum systemd_timer_probe.py >"$probe_capture/source.sha256"
  cp systemd_timer_probe.py "$probe_capture/"
  date -u +%Y-%m-%dT%H:%M:%SZ >"$probe_capture/run.started"
  python3 systemd_timer_probe.py >"$probe_capture/driver.stdout" \
    2>"$probe_capture/driver.stderr"
  probe_rc=$?
  date -u +%Y-%m-%dT%H:%M:%SZ >"$probe_capture/run.finished"
  printf '%s\n' "$probe_rc" >"$probe_capture/driver.exit"
  exit "$probe_rc"
)

Keep this terminal running while performing the read-only capture below. A changed source needs its own digest and fresh run. Never attach the supplied digest to an edited driver.

Capture one baseline activation and stop with margin

T1 establishes that all three profiles can reach the recorder before the interruption. Require exactly one complete JSONL row for each profile, then require its service to settle. The expected settled states are inactive for the successful workers and failed for on-fail. A row appearing while the process is still running is insufficient.

The timer manual's active-unit rule matters here: an already-active target is left running when its timer elapses. Settling the short oneshots and using RemainAfterExit=no removes that competing explanation for a missing later recorder invocation.

Review first_service_state rather than relying entirely on the driver's assertions. Require Result=success and ExecMainStatus=0 for off and on; require Result=exit-code and ExecMainStatus=1 for on-fail. Some baseline outcome fields are captured without being asserted by the unchanged source.

The driver later deletes its unit files. During the inactive interval, use a second Bash terminal to retain their actual contents. Identify this run's shared token from the listing, then enter that token and the outer capture directory printed earlier:

systemctl --user list-unit-files 'refonte-persistent-*.timer' --no-legend
read -r -p 'Outer capture directory: ' probe_capture
read -r -p 'This run token: ' probe_token
[[ -d "$probe_capture" && "$probe_token" =~ ^[0-9a-f]{10}$ ]] || exit 1
probe_units=()
for probe_profile in off on on-fail; do
  probe_units+=("refonte-persistent-${probe_token}-${probe_profile}.service")
  probe_units+=("refonte-persistent-${probe_token}-${probe_profile}.timer")
done
date -u +%Y-%m-%dT%H:%M:%SZ >"$probe_capture/unit-capture.started"
systemctl --user --no-pager cat "${probe_units[@]}" \
  >"$probe_capture/unit-files.txt" 2>"$probe_capture/unit-files.stderr"
probe_cat_rc=$?
printf '%s\n' "$probe_cat_rc" >"$probe_capture/unit-files.exit"
test "$probe_cat_rc" -eq 0 || exit "$probe_cat_rc"

Match all six captured names to the eventual evidence.json profiles and check their worker paths. Unexpected drop-ins, a partial capture, or another run's token requires HOLD. The systemctl description of cat distinguishes backing files from the manager's loaded understanding; retain both these contents and the driver's raw show output.

Do not let a missed stop window contaminate the count

Acceptance requires the recorded stopped_at to be at least five seconds before T2. The driver's earlier margin check precedes further introspection and the stop command, so that check alone does not prove the stop completed in time.

If the actual margin is missing, discard the run as acceptance evidence while retaining it for diagnosis. Start a fresh experiment with new names and dates. Do not edit timestamps, remove an inconvenient activation, or subtract an assumed ordinary tick. Those actions would change the evidence without recreating the controlled condition.

Restart only after every remaining calendar instant has passed

Leave all three timers inactive through T2, T3, and T4. The driver checks that each recorder still has exactly its baseline row before restarting. Any additional row during this interval means the proposed inactive-window condition was not established, regardless of whether a later total happens to match the manifest.

Restart begins only after T4 plus the five-second laboratory margin. Review the UTC schedule against the second timer-start command's recorded started timestamp. The restarted_at field is written after that command returns; an immediate catch-up invocation may legitimately precede that field. Use the command interval to interpret activity, rather than treating its completion timestamp as the beginning of activation.

For each persistent profile, the expected second row should be consistent with that restart interval or the subsequent observation window. A row timestamp is the worker's reading of the wall clock, so interpret it together with the sampled clock guard and command sequence. It does not independently establish which actor requested the service start.

Remove future ticks from the explanation

A repeating hourly or minutely expression would leave a competing explanation: an ordinary scheduled instant could arrive after restart and produce the apparent catch-up row. Four explicit, fully dated instants remove that ambiguity for this proposed test. Once T4 has passed, none of the declared calendar expressions has another future match.

Verify that the effective timers actually contain those four instants and no additional activation settings. The retained definitions and raw properties must agree. Do not treat the dates in the Python source as proof that an unexpected drop-in was absent.

Maintain exclusive operational control over the test names. No other terminal, automation, or operator should start these services during the observation. If another start source is possible, retain the rows but HOLD the causal conclusion. Finite scheduling removes future calendar ticks from consideration; it does not turn a recorder UUID into proof of its triggering mechanism.

Compare persistent and nonpersistent profiles

Freeze the expected result manifest before reviewing any output. The three case IDs refer to configuration and intended worker behavior, not observations from the commissioning environment:

Metric

off

on

on-fail

Persistent

false

true

true

Baseline invocations

1

1

1

Missed instants

3

3

3

Added catch-up invocations

0

1

1

Final invocation total

1

2

2

Successful recorder exits expected

1

2

0

Observed total

NOT RUN

NOT RUN

NOT RUN

The success counts additionally require the service-state review; they cannot be established by counting rows. Keep every case separate so that an extra invocation in one profile cannot cancel a missing invocation in another.

The Persistent=false comparison supplies the control for the restart itself. Under the authored conditions, off should retain its baseline total because all scheduled dates have expired. Its stopped interval is identical to that of on; persistence is the intended scheduling difference.

For on, the proposed increase is one invocation after three missed instants. This is the bounded interpretation of systemd timer catch-up behavior being tested. A queue that replayed every missed scheduled run would require a different observed pattern. Do not relabel one catch-up invocation as three completed runs simply because the worker could theoretically perform more work internally.

Require distinct activation_id values within each profile. These UUIDs identify recorder rows created by separate worker executions in this fixture; they are not systemd invocation IDs or business-period identities. Retain the PID and UTC fields as corroborating context without using PID uniqueness as the count criterion.

If the values differ, record the discrepancy as found. Inspect the effective configuration, service state, command failures, and timing before proposing another experiment. Agreement would support the local finite-calendar comparison. It would not establish conformance for every distribution, timer configuration, or workload.

Keep the failing service in the evidence ledger

The on-fail worker writes its row, flushes it, requests a filesystem sync, and then exits with status 1. Its purpose is to produce invocation evidence while deliberately failing the service outcome. Removing that profile would leave the central success ambiguity untested.

The systemd v257 service manual source describes oneshot behavior and RemainAfterExit. With the fixture's Type=oneshot and RemainAfterExit=no, a completed successful worker is not retained as an active service. That supports repeating the short recorder without intentionally leaving the previous activation active.

Use the two service snapshots to review the expected outcomes:

Case

Expected baseline result/status

Expected final result/status

Meaning

off

success / 0

success / 0

One successful recorder completion

on

success / 0

success / 0

Baseline and catch-up recorder completions

on-fail

exit-code / 1

exit-code / 1

Both recorded invocations deliberately fail

These remain expectations until the matching invocation rows and settled states are observed. Final service properties describe the latest retained state; they do not independently enumerate every earlier process result. That is why the baseline snapshot must survive the later activation.

Triggered does not mean completed successfully

The JSON field requested_exit records what the worker intends to do next. It is written before termination. An operator must check the manager's actual exit evidence rather than turning that field into a success counter.

Likewise, a timer's last-trigger timestamp concerns triggering. The proposed failing comparison expects trigger history to advance while the service remains unsuccessful. Inspect the before-and-after timer properties directly; the driver captures them but does not assert their advancement.

Even the successful recorder profiles prove only their deliberately small operation. A real backup job would need its own outcome checks; this laboratory performs none. A trigger timestamp, a JSON row, an exit status, and an accepted business result answer different questions.

This separation also applies to configuration automation practice. Reapplying an approved unit definition can restore configuration. It does not create evidence that each missed scheduled instant produced successful work. Preserve the intentional failure throughout the comparison so the final scheduling decision cannot quietly become a service-success claim.

Account for timing controls without promising precision

The fixture sets RandomizedDelaySec=0 and AccuracySec=1ms to reduce scheduling ambiguity in a short experiment. The v257 timer source's timing options distinguish added randomized delay from the accuracy window used to coalesce timer expirations. Neither setting promises when Python will execute its first instruction or append a row.

These are laboratory controls. Do not copy them into a production schedule merely because the comparison uses them. A workload owner's normal delay and accuracy requirements need separate review, including what constitutes an acceptable late opportunity to run.

The driver compares elapsed wall-clock time with elapsed monotonic time and raises HOLD when the sampled difference exceeds two seconds. It also uses polling watchdogs around expected rows and service settling. That makes a stalled or timing-disrupted experiment visible in many ordinary failure paths.

However, the guard is sampled, not continuous. Blocking systemctl calls and the final three-second sleep sit outside those checks. Moreover, wait_for() examines its deadline after a false predicate; a slow predicate that eventually returns true can finish beyond the nominal budget. Interpret these controls as diagnostic limits, not absolute end-to-end deadlines.

The command recorder has a further limit: it appends a command entry only after the subprocess returns. A timeout can therefore leave an exception without a complete command record. Partial subprocess output may also be unavailable. Preserve the outer stderr and nonzero exit status, label the attempt HOLD, and do not invent a return code or completion timestamp for the interrupted command.

Keep the VM awake and the user session available throughout the run. Do not adjust clocks, pause the VM, or introduce artificial delays to manufacture the desired count. Preserve any observed drift, slow command, suspend event, or unexplained scheduling delay as a reason to HOLD the affected conclusion.

The final observation pause bounds how long the driver watches after the expected counts appear. It does not prove that no event could ever occur later under a changed environment. Acceptance comes from the finite schedule, isolated unit ownership, recorded sequence, and reviewed observation window together. A stopwatch alone cannot supply that evidence.

Inspect the persistent state without treating it as a queue

Read the complete timer show output captured before stopping and after the final service checks. Identify each exact timer and compare its last-trigger properties, including LastTriggerUSec, exposed in the official 257-series D-Bus interface reference. Keep the raw values even if a separate review converts them to UTC for comparison.

For on and on-fail, the authored expectation is a trigger associated with T1 and a later catch-up trigger associated with reactivation. Both state progression and recorder rows must be reviewed. The supplied driver neither copies the on-disk timestamp file nor explicitly asserts that the displayed last-trigger property advanced.

That distinction limits the claim. A future passing run can show agreement among its configuration, timer properties, and worker outcomes. It cannot certify every detail of on-disk state persistence, filesystem durability, or manager behavior after a reboot. Those layers were not exercised by stopping and restarting these timers.

A last-trigger timestamp is not a replay ledger

A single latest timestamp cannot identify three separate missed invocations, their parameters, or their results. Several different histories can share the same latest trigger: one missed instant and several missed instants can both lead to a later catch-up opportunity. The finite input schedule supplies the missing count in this laboratory; the timer state alone does not.

The recorder does not receive T2, T3, or T4 as a requested business period. Its UTC field describes when its logging code ran. Reinterpreting that value as an original scheduled period would add meaning that the fixture never supplied.

Preserve the evidence in its original roles: the manifest establishes declared instants, the command sequence brackets inactivity, the timer properties describe trigger state, and the worker/service records describe invocation and termination. Do not reconstruct nonexistent queue entries from those fields.

If a property is absent, unparsable, or inconsistent with the matching snapshots, HOLD that comparison. Do not fill the gap with the expected value. In particular, a recent persistent timestamp alongside a failed on-fail service should remain visible as a deliberate demonstration of separate trigger and outcome evidence.

Classify a workload that needs one run per period

Ask the workload owner to complete one sentence: after inactivity, an acceptable next execution must do what? The answer determines whether the tested contract is useful. A frequency such as hourly does not, by itself, state whether every hour is an independently owed result.

A current-state refresh might accept one opportunity to update its present view after an interruption. That is a candidate for ACCEPT_COALESCED_TRIGGER, provided the owner also reviews failure handling and the permitted delay. The example is a proposed requirement classification, not a claim that every refresh job is safe to coalesce.

A workload that requires a distinct execution for each expired period cannot rely on the expected on total. Under that requirement, choose REPAIR_SCHEDULING_CONTRACT. The observed scheduling mechanism could be behaving consistently with its documentation while still being unsuitable for the application requirement.

An application might perform multiple pieces of work inside one invocation, but this recorder does not. Do not infer that capability from a shell command name, successful exit, or installed timer setting. Any claim that one run reconciles several periods needs its own design, inputs, and outcome qualification by the application owner.

The repair decision is therefore a requirements handoff. State the required unit of work, treatment of missed periods, acceptable lateness, and responsibility for failures. Give that requirement to the application or queue owner. An explicit queue or period ledger is a separate design task; no implementation or historical replay is authorized by this timer experiment.

Connect this classification to broader administration responsibilities: operations must understand what the service owner needs before changing how it is scheduled. A technically valid setting can implement the wrong contract. Hold deployment when the requirement remains ambiguous, even if the laboratory counts agree perfectly.

Record the owner's answer beside the evidence, so a later reviewer does not have to infer business obligations from the timer configuration.

Clean up only the units and state owned by the test

The driver's normal finally path attempts to stop its recorded timers and services, clean persistent state for the two persistent timers, unlink the unit files it recorded as created, and reload the user manager. It leaves the evidence directory and recorder logs available for review.

The v257 systemctl clean documentation permits removal of a persistent timer's timestamp data with clean --what=state and requires the unit to be stopped. This protocol applies that operation to the exact timer names it created. It does not clean another service's state directory or use a wildcard for deletion.

Review cleanup_errors, the command return codes, and the final process status. A cleanup problem changes the evidence status to HOLD_CLEANUP, retaining the preceding status separately when possible. A diagnostic count match followed by failed cleanup is not a completed laboratory run.

The cleanup code attempts recovery; it cannot guarantee cleanup after every interruption or filesystem error. An uncatchable termination can prevent finally from running, and an early failure may leave an incomplete record. Preserve the outer transcript and inspect the owned names before deciding what remains.

If owner-directed cleanup is needed, use the captured six-name manifest and verify each resource belongs to this run. Stop the identified units before trying state cleanup again. Never broaden the target to every persistent timer merely because a test command failed.

Archive both capture directories together before disposing of the VM: the outer transcript and unit contents, plus the driver's evidence.json, worker source, and JSONL files. Temporary paths are convenient for the experiment, not an archival policy.

Cleaning timestamp state retires this test's scheduling memory. It does not recreate missed work, restore a business result, or roll a production application back to an earlier point. Keep those ownership decisions separate from laboratory housekeeping.

Choose accept, repair or hold from the evidence

Apply the decision to an explicitly named workload requirement. Passing the fixture's count assertions is one input; the independent review must also validate source identity, manager identity, preserved definitions, timing, service outcomes, trigger-state comparison, and cleanup.

Evidence and requirement

Decision

Operational interpretation

Complete local evidence agrees; owner accepts one coalesced opportunity

ACCEPT_COALESCED_TRIGGER

Accept only that limited scheduling contract

Every missed period requires its own execution

REPAIR_SCHEDULING_CONTRACT

Refer the unmet requirement to the application or queue owner

Expected on-fail exits are observed but successful work is claimed

HOLD

Retain the useful failure control; reject the success claim

Actual workload service fails or lacks outcome evidence

HOLD

Investigate service execution before claiming useful recovery

User manager unavailable or protocol unexecuted

HOLD

Preserve NOT RUN; no activation observation exists

Missing/duplicate rows, clock uncertainty, or missed timing margin

HOLD

Repeat only as a fresh controlled experiment

Missing captures, cleanup failure, or source/version disagreement

HOLD

Preserve the discrepancy and resolve the unsupported claim

Hold an unexecuted expectation outside the observation column

Keep four evidence classes explicit when reviewing a run. Documented behavior comes from the cited official manuals. Authored expectations are the finite schedule and proposed 1/2/2 invocation totals. Supplied commissioning observations comprise the reported client build, unavailable user bus, and source compilation. They contain no timer run.

Source identity and syntax checks do not execute main(), contact a manager, start a service, or wait across the schedule. No new behavioral run accompanies this article. The timer integration remains NOT RUN; its expected counts require a new execution on the stated disposable VM.

A future observation column must cite its own run directory, exact source digest, actual manager version, transcript, and reviewed records. A printed PASS_3_PROFILES_FINITE_CALENDAR string in source is not an observation. Even an executed PASS cannot waive a missing external capture or an independently detected timing problem.

Where documentation, a versioned source, and execution disagree, retain all three. HOLD the disputed conclusion until the disagreement is understood. Do not replace an inconvenient measurement with the expected table.

Assign operational ownership before changing schedules

The scheduling decision should name who owns each claim and action. One person may hold several responsibilities, but the review must still distinguish the configuration being changed from the work that configuration is intended to initiate.

Owner

Responsibility before an operational change

Unit maintainer

Identify exact manager, unit names, configuration revision, and effective settings

Service owner

Define meaningful success and investigate unsuccessful invocations

Workload owner

Decide whether coalesced opportunities satisfy the required work

Evidence reviewer

Check the manifest against retained records and identify unresolved gaps

Change owner

Approve the concrete schedule change and the configuration rollback boundary

The handoff should include the decision vocabulary, its scope, and its evidence location. For example, accepting coalesced triggering should state what interruption scenario was qualified and which workload requirement permits it. It should also state that service success and historical business completeness need their own evidence.

For REPAIR_SCHEDULING_CONTRACT, describe the missing capability precisely: the owner requires one execution per missed period, while the timer comparison qualifies only a catch-up opportunity. Do not hand over an instruction to delete state or repeatedly restart a service. Those actions do not specify which historical work is owed.

Configuration rollback must restore only an approved revision through its owner. Retain the prior unit contents and review the newly loaded properties after restoration. A file rollback can change future scheduling behavior; it does not undo a service's external effects or restore an earlier business history.

Likewise, do not assume that returning Persistent to a previous value restores previous trigger state. The owner must decide how retained state is handled, with its evidence preserved. This laboratory's timestamp cleanup is appropriate only for retiring its uniquely named test units.

These habits belong in administration learning and troubleshooting: reproducible observations, precise ownership, and a recoverable configuration history. A reviewer should be able to identify the approved next action without guessing whether a timer timestamp means the application succeeded.

Leave any unresolved outcome with a named owner and a HOLD decision. A technically complete test can still expose an incomplete workload requirement.

Develop broader administration and recovery practice

Use the retained experiment as a review exercise. Ask another administrator to reconstruct the four instants, identify the actual stop and restart interval, and explain the difference between the successful and failing profiles without looking first at the driver's final status. The exercise is useful only when the reviewer can trace each conclusion to the relevant record.

Then ask what remains unknown. Can the packet establish a particular business result? Does it contain any execution on a rebooted manager? Did it test a production workload's failure recovery? The correct answers follow the experiment's boundaries. Recognizing an unsupported conclusion is part of competent administration, even when the underlying command completed successfully.

Broader practice should connect command-line work, service supervision, log interpretation, networking, troubleshooting, and backup verification. Those skills help an administrator recognize when a scheduling question belongs to the unit maintainer and when an application owner must define the missing outcome. Keep exercises small enough that another person can review their inputs and reproduce their evidence.

The useful deliverable from this particular exercise is a defensible decision record: which scheduling opportunity the owner accepts, which stronger requirement needs another design, and which missing evidence prevents approval. Preserve the deliberately failing case when teaching that distinction. It prevents a neat activation count from becoming an unsupported recovery claim.

Refonte Learning's program page describes Windows/Linux administration, networking and security, troubleshooting, backup and disaster recovery, and command-line practice. These are broader administration topics; they do not establish that this exact timer laboratory is included in a cohort's curriculum.

For broader Linux/Windows administration, troubleshooting and backup practice, review the System Administration Program; confirm the applicable cohort duration directly.