Project 8: Boot-Resilient Supervised Service

Build a runit-supervised PocketOps worker whose accepted jobs survive child crashes, whole-process-tree death, delayed boot delivery, offline periods, and reboot without duplicate logical effects.

Quick Reference

Attribute Value
Difficulty Level 3 - Advanced
Time Estimate 16-26 hours
Language Shell service definitions plus a Python worker (alternatives: Go, Rust)
Prerequisites Projects 1, 5, and 7; processes/signals; SQLite transactions; queues and idempotency
Key Topics Termux:Boot, termux-services, runit, foreground workers, leases, retry, dead letters, wake locks, Doze, recovery evidence

1. Learning Objectives

By completing this project, you will:

  1. Distinguish desired service state, supervisor state, child process state, and durable work state.
  2. Explain what runit can restart and why it cannot resurrect itself after Android kills the Termux process tree.
  3. Design a short, ordered, idempotent boot entry point.
  4. Keep a supervised worker in the foreground and use signals only as advisory graceful-stop paths.
  5. Implement leased queue work whose ownership expires after a dead worker.
  6. Prevent duplicate logical side effects across ambiguous kill points with stable idempotency and reconciliation.
  7. Bound crash loops, retries, poison work, log growth, concurrency, and wake-lock scope.
  8. Demonstrate recovery under child kill, supervisor/process-tree kill, reboot, offline state, and low-battery policy.

2. Theoretical Foundation

2.1 Core Concepts

Supervision is desired-state enforcement inside a living process tree. termux-services uses runit. A service definition describes how to start a foreground process; enabling it records desired state; runsvdir watches service directories; runsv starts/observes each child; log services capture output. If the worker exits, its supervisor can restart it. If Android kills Termux, runsvdir, runsv, loggers, and workers can all disappear together. A supervisor is not an Android exemption.

Daemonization works against supervision. A traditional daemon may fork, detach, close standard streams, and make the parent exit. From runit’s perspective, that can look like the service ended even though an untracked descendant continues. The worker must remain in the foreground, report through standard streams or an explicit log adapter, and let runit own restart. One supervisor should have one clear process to observe.

Boot is a trigger, not an uptime guarantee. On the classic Termux track, Termux:Boot receives a boot broadcast after the user has installed and launched the add-on once, then executes ordered files in ~/.termux/boot/. Android/OEM restrictions may delay or prevent delivery. A boot entry point should construct a minimal environment, verify/migrate durable state idempotently, start the service supervisor if absent, report evidence, and exit. It should not perform the entire queue or assume interactive shell profiles.

Durability lives in state transitions. A producer transactionally inserts a job and returns success only after commit. A worker claims it with a lease containing owner and expiry. The job moves through explicit states such as ready, leased, retry_wait, succeeded, permanent_failed, and dead_letter. If a worker dies, an expired lease makes the job eligible again. A cleanup handler may release a lease early during a normal stop, but correctness cannot depend on that handler running.

Side effects create an ambiguous window. Suppose a remote service accepts an action, then the phone dies before recording success. On restart, local state still says leased or unknown. Replaying with a new request identity duplicates the effect. The worker therefore derives one stable idempotency key per logical job and sends it to capable external systems. When the target has no idempotency support, the worker needs a reconciliation query or must classify the result as AMBIGUOUS for human action. There is no universal exactly-once transport trick.

Retry and crash-loop policy protect the phone. Transient network errors may retry with capped exponential backoff and jitter. Missing configuration or invalid input is permanent. A poison job exceeds an attempt/age policy and moves to dead letter. A service that exits immediately can also enter a crash loop; repeated process restart consumes battery and hides the fault. The service needs startup validation, restart-rate evidence, and a circuit/degraded state that stops expensive churn.

Wake locks are narrow energy authority. termux-wake-lock can help keep the CPU active for justified visible work, but it does not guarantee that Android preserves the app, network, or boot delivery. Acquire only around bounded work that cannot safely pause, record why it is held, and guarantee a release path during normal operation. Most queue correctness should survive sleep instead of fighting it.

Health is more than PID existence. Liveness asks whether a process responds. Readiness checks schema, storage, queue, configuration, and required capabilities. Degradation explains partial operation—for example, low-battery policy pauses expensive sync while local intake remains ready. Operational health includes desired state, runsvdir, child PID/uptime, restart count, heartbeat age, queue depth/oldest age, dead letters, last abnormal exit, disk headroom, last boot start, expired leases, and duplicate count.

Logs are another queue. Unbounded standard output can fill Termux-private storage and stop the application. Use structured records, bounded fields, rotation by size/count/time, redaction, and storage-headroom checks. Preserve enough boot/supervisor evidence to distinguish a child crash loop from disappearance of the whole process tree.

2.2 Why This Matters

Android owns process lifetime. Reliable phone automation comes from recoverable durable state, not from keeping one process alive forever. The same concepts power job queues, edge agents, backup workers, message relays, sync engines, and server process managers. This project also forces a mature definition of “done”: the service must prove recovery under intentional destruction, not merely run overnight once.

2.3 Historical Context / Background

Classic Unix daemons often detached and wrote PID files because no dedicated supervisor owned them. Supervision suites such as runit, daemontools, and later service managers made foreground processes and explicit desired state preferable. Queue systems separately developed leases, visibility timeouts, dead-letter queues, and idempotent consumers. Termux combines these server concepts inside an Android application process, where the outer lifecycle remains controlled by the mobile OS.

2.4 Common Misconceptions

  • runit makes a daemon immortal.” It restarts children only while its own process tree lives.
  • “Boot runs immediately after every reboot.” Broadcast delivery depends on Android state, app restrictions, and OEM policy.
  • “A wake lock disables Doze.” It does not override every network, app, or process restriction.
  • “A signal handler guarantees cleanup.” Force-stop, kill, power loss, or process-tree death may bypass it.
  • “If the job is leased, it cannot run twice.” A dead worker’s lease must expire, and an ambiguous side effect may be replayed.
  • “A PID proves health.” A stuck worker can have a PID and make no progress.
  • “Retry forever is resilient.” It creates battery drain, log growth, and concealed poison work.

3. Project Specification

3.1 What You Will Build

Build a PocketOps worker service with these surfaces:

  1. Boot adapter: A minimal ordered Termux:Boot entry point that starts service supervision idempotently.
  2. Service definition: A runit foreground worker and bounded log service managed through termux-services.
  3. Durable queue: Leases work from Projects 5 and 7, records attempts/results, retries economically, and dead-letters poison items.
  4. Health command: Reports desired/supervisor/service/work/power/storage truth.
  5. Acceptance harness: Records child-kill, process-tree-kill, reboot, offline, low-battery, and ambiguous-side-effect evidence.

The worker is useful only while Termux can run. It makes pending state safe while it cannot.

3.2 Functional Requirements

  1. Short boot path: Verify state, start supervisor if needed, record result, and exit.
  2. Idempotent startup: Repeated boot/reopen/start calls do not duplicate supervisors, migrations, or work.
  3. Visible desired/actual state: Distinguish enabled marker, runsvdir, runsv, worker, and logger.
  4. Foreground worker: No self-daemonization or hidden descendant process.
  5. Transactional leasing: One worker owns a job until bounded lease expiry/renewal.
  6. Safe replay: Stable idempotency identity or explicit reconciliation protects external effects.
  7. Bounded retry: Classify permanent/transient/capability-wait; apply cap, jitter, attempt/age ceiling.
  8. Dead letters: Poison work remains inspectable and can be reviewed/requeued explicitly.
  9. Crash-loop evidence: Count/reason about rapid exits and enter visible degraded mode.
  10. Power policy: Pause expensive optional work under low-battery policy while retaining intake and health.
  11. Wake-lock discipline: Justification, bounded scope, status evidence, and release plan.
  12. Rotated logs: Enforce size/count/retention and storage-headroom guard.
  13. Recovery health: Record expired leases, last boot, last abnormal exit, and duplicate logical effects.

3.3 Non-Functional Requirements

  • Durability: Accepted work survives abrupt worker/process-tree death and reboot.
  • Correctness: No state is reported complete before its result commits.
  • Energy: Concurrency, retry, wake locks, polling, and restart loops are bounded.
  • Observability: A user can localize failure to boot delivery, supervisor, child, queue, capability, or policy.
  • Security: Service files/config/state remain private; logs redact secrets; no broad network exposure is introduced.
  • Honesty: Documentation states that Android may prevent or delay background execution.

3.4 Example Usage / Output

$ pocketctl service status worker
Desired     enabled
Supervisor  runsvdir up
Service     up pid=18422 uptime=00:03:17 restarts=1
Queue       ready=0 leased=0 retry=0 dead_letter=1
Last exit   code=23 reason=TRANSIENT_NETWORK
Recovery    last_boot=ok expired_leases=1 duplicates=0
Power       DEGRADED battery_policy=pause-expensive

3.5 Real World Outcome

Enqueue five controlled jobs: two local successes, one idempotent remote effect, one transient network failure, and one poison item. Enable/start the service and capture health. Kill only the worker during a lease; runit restarts it and the lease safely recovers. Kill the complete Termux process tree after the remote effect is accepted but before local acknowledgement; reopening Termux or rebooting restarts supervision, reuses the stable idempotency key, and records no duplicate logical effect. The transient item waits/retries after connectivity returns; the poison item reaches dead letter after bounded attempts. Low-battery policy pauses expensive work while health/intake remain available. This destruction-and-recovery run is the project’s single observable outcome.


4. Solution Architecture

4.1 High-Level Design

 Android BOOT_COMPLETED / user reopens Termux / manual start
                          |
                 minimal idempotent adapter
                          |
                    start runsvdir once
                          |
                 +--------v---------+
                 | runit supervision |
                 | runsv + logger    |
                 +--------+----------+
                          |
                  foreground worker
                          |
                   lease one job
                          |
             +------------v-------------+
             | private durable queue     |
             | ready -> leased -> done   |
             |      -> retry -> DLQ      |
             +------------+-------------+
                          |
           bounded side effect + stable idempotency key
                          |
                  commit result, then ack

 Android may kill worker, supervisor, or every process above.
 Restart path recovers expired leases from durable state.

4.2 Key Components

Component Responsibility Key Decisions
Boot adapter Start supervision after boot/relaunch Minimal, ordered, idempotent, noninteractive environment
Supervisor bootstrap Ensure one runsvdir Verify actual process, not only enabled marker
Service definition Launch one foreground worker No self-daemonization; explicit environment
Log service Capture/rotate bounded records Preserve crash evidence without filling storage
Queue repository Transactional state transitions Private SQLite with uniqueness and leases
Worker loop Lease/execute/commit/ack one bounded unit Limited concurrency and heartbeat
Retry classifier Choose retry/wait/permanent/DLQ Battery-aware capped backoff
Idempotency/reconciler Resolve ambiguous external effect Stable identity; never invent new ID on retry
Power adapter Decide expensive-work eligibility Wake lock only for justified bounded work
Health aggregator Report every lifecycle layer Liveness, readiness, degradation, recovery evidence

4.3 Data Structures

Job
  job_id, schema, action, payload_ref, idempotency_key
  state, created_at, max_attempts, max_age

Lease
  job_id, owner_run_id, acquired_at, expires_at, heartbeat_at?

Attempt
  attempt_id, job_id, run_id, started_at, ended_at?
  outcome_code?, remote_receipt?, safe_error?

ServiceRun
  run_id, boot_id?, started_at, last_heartbeat
  exit_code?, exit_reason?, wake_lock_reason?

RetryPlan
  class, next_attempt_at?, attempt_count, terminal_reason?

HealthSnapshot
  desired, supervisor, worker, logger
  queue_counts, oldest_age, restarts, dead_letters
  power_state, disk_headroom, last_boot, expired_leases, duplicates

4.4 Algorithm Overview

Key Algorithm: Lease, Side Effect, Commit, Acknowledge

  1. Validate runtime schema/config and recover expired leases.
  2. If power/capability policy forbids this action, leave it eligible for a later bounded time and report degradation.
  3. In one transaction, select an eligible job and create a lease/attempt.
  4. Execute a bounded action with the job’s stable idempotency key.
  5. If success or known prior success, transactionally store receipt/result and mark succeeded.
  6. If failure, classify permanent, transient, capability-wait, ambiguous, or poison.
  7. Transactionally set retry time, dead letter, or reconciliation-required state.
  8. Release resources and continue within concurrency/energy policy.

Complexity Analysis

  • Time: Per job is bounded by its action deadline; retry delays do not occupy a worker.
  • Space: O(J + A) for retained jobs J and bounded attempt history A; logs and completed history have explicit retention.

5. Implementation Guide

5.1 Development Environment Setup

Use the classic reference track with Termux:Boot from the same supported source/signing family as Termux. Launch the Boot add-on once as required by its official setup. Install termux-services, restart/open a fresh shell so its supervisor bootstrap is available, and learn the official sv-enable, sv-disable, sv up, sv down, and service-log locations.

Keep service code, database, configuration, and logs under Termux-private storage. Use Project 1 to emit the actual HOME/PREFIX/track/capabilities. Disable or isolate real remote effects during destructive tests; use a controlled idempotent test receiver. Record OEM battery configuration but do not prescribe disabling platform safeguards as the design.

5.2 Project Structure

boot-resilient-service/
|-- src/
|   |-- worker/
|   |-- queue/
|   |-- retry/
|   |-- reconcile/
|   |-- health/
|   `-- power/
|-- service/
|   |-- run.shape           # foreground launch contract, not a full script
|   `-- log-run.shape       # rotation/retention contract
|-- boot/
|   `-- start-services.shape
|-- tests/
|   |-- queue-state-machine/
|   |-- kill-points/
|   |-- crash-loop/
|   `-- android-acceptance/
|-- docs/
|   |-- recovery-matrix.md
|   |-- wake-lock-policy.md
|   `-- runbook.md
`-- README.md

5.3 The Core Question You Are Answering

“How do I guarantee accepted work survives when neither the worker, its supervisor, nor the phone stays continuously alive?”

The answer cannot be “keep it running.” It must name the durable commit, lease expiry, idempotency boundary, restart triggers, and evidence at every ambiguous kill point.

5.4 Concepts You Must Understand First

  1. Process supervision
    • What are runsvdir, runsv, service desired state, and a log service?
    • What disappears when Android kills Termux?
    • Reference: termux-services README and runit documentation.
  2. Signals and process lifetime
    • Which signals can be handled, and which termination paths bypass cleanup?
    • Why must the worker remain in the foreground?
    • Book reference: Kerrisk, Chapters 20-26 and 37.
  3. Leases and durable queues
    • Why is a PID not a durable ownership record?
    • Who reclaims expired work, and under what clock assumptions?
    • Book reference: “Designing Data-Intensive Applications,” Chapter 8.
  4. Android background limits
    • How do Doze, App Standby, OEM policy, boot delivery, and process limits differ?
    • Why does wake lock not equal process exemption?
    • Reference: Current Android background-work and Termux lifecycle documentation.

5.5 Questions to Guide Your Design

  1. What is the smallest boot action, and can it run twice safely?
  2. How do you prove runsvdir exists before blaming a child service?
  3. What lease duration covers normal work without delaying crash recovery too long?
  4. Does a lease use monotonic or wall-clock data across reboot, and how is expiry reconciled?
  5. What happens at each kill point around the external side effect?
  6. Which results can retry, wait for capability, reconcile, or dead-letter?
  7. When is a wake lock justified, and how is its normal release observed?
  8. How much private storage may logs, attempts, and completed jobs consume?

5.6 Thinking Exercise

The Five Kill Points

Trace one job through:

  1. death before lease;
  2. death after lease but before side effect;
  3. death after remote accept but before result commit;
  4. death after result commit but before acknowledgement/cleanup;
  5. death after completion.

For each point, write durable rows, next restart behavior, duplicate risk, lease action, and visible health. Add a target without idempotency support and decide where automatic replay must stop for reconciliation.

5.7 The Interview Questions They Will Ask

  1. “What is process supervision, and why keep a service in the foreground?”
  2. “What can runit recover from, and what can it not recover from alone?”
  3. “How do leases recover abandoned work?”
  4. “What is a poison message or dead-letter queue?”
  5. “How do Doze and wake locks interact?”
  6. “How do you prevent duplicate side effects after an ambiguous crash?”

5.8 Hints in Layers

Hint 1: Prove each process layer

Check desired marker, runsvdir, runsv, worker PID/heartbeat, and logger in order. Do not treat sv-enable output as proof the supervisor is alive.

Hint 2: Use a foreground no-op fixture first

Before integrating the real queue, supervise a bounded fixture that stays in the foreground and emits a heartbeat.

Hint 3: Journal every transition

Lease, attempt, result, retry, and acknowledgement are transactions. Signals only speed normal cleanup.

Hint 4: Destruction is the acceptance test

Automate or carefully script kill/reopen/reboot scenarios against controlled effects and retain timestamps, receipts, queue rows, and health snapshots.

5.9 Books That Will Help

Topic Book Chapter
Signals and child processes “The Linux Programming Interface” Chapters 20-26
Daemons and process limits “The Linux Programming Interface” Chapters 36-37
Distributed failure “Designing Data-Intensive Applications” Chapter 8
Production stability “Release It!,” 2nd ed. Stability patterns, timeouts, circuit breakers

5.10 Implementation Phases

Phase 1: Supervisor and Health Layers (4-6 hours)

Goals: Make service desired/actual state and logs observable.

Tasks:

  1. Define minimal Boot and foreground service contracts.
  2. Prove runsvdir, child restart, logger, and manual start/stop.
  3. Build health aggregation for each layer.

Checkpoint: Killing the fixture child produces one observed restart; killing the supervisor produces a distinct failed health state.

Phase 2: Durable Queue and Ambiguous Effects (6-10 hours)

Goals: Make work recoverable at every kill point.

Tasks:

  1. Implement the queue state machine, leases, attempts, and expired-lease recovery.
  2. Add stable idempotency/reconciliation and bounded retry/dead-letter policy.
  3. Run the five-kill-point matrix against a controlled receiver.

Checkpoint: Every kill leaves a deterministic recoverable state and no duplicate logical receipt.

Phase 3: Boot, Power, and Destructive Acceptance (6-10 hours)

Goals: Prove whole-tree/reboot recovery under mobile constraints.

Tasks:

  1. Add the idempotent Boot adapter and record boot/reopen evidence.
  2. Add low-battery, offline, wake-lock, crash-loop, retention, and headroom policies.
  3. Complete the Real World Outcome across worker kill, tree kill, and reboot.

Checkpoint: Recovery evidence is complete and documentation states observed device behavior, not a universal guarantee.

5.11 Key Implementation Decisions

Decision Options Recommendation Rationale
Worker process Self-daemonize; foreground Foreground Lets runit observe real state
Startup Complex boot job; minimal idempotent adapter Minimal adapter Boot can repeat, delay, or be interrupted
Work ownership PID file; durable lease Durable lease Recovers after process/reboot loss
Side-effect identity New ID per attempt; stable job key Stable job key Prevents duplicate logical effects
Unsupported idempotency Blind replay; reconcile/ambiguous Reconcile/ambiguous Honest under uncertain remote outcome
Retry Immediate forever; capped typed policy Capped typed policy Protects battery and visibility
Poison work Retry forever; dead letter Dead letter Preserves evidence without crash churn
Wake lock Always held; bounded justified scope Bounded scope It is energy authority, not uptime insurance

6. Testing Strategy

6.1 Test Categories

Category Purpose Examples
State machine Prove legal durable transitions Ready, lease, retry, success, permanent fail, DLQ
Supervisor Prove desired/actual behavior Child exit, foreground requirement, supervisor missing
Kill-point Prove interruption safety Before/after lease, side effect, commit, ack
Retry Bound energy and poison work Jitter, cap, max age/attempts, crash loop
Logging/storage Prevent self-inflicted outage Rotation, redaction, low headroom
Android acceptance Prove outer lifecycle behavior Process-tree kill, reopen, reboot, offline, low battery

6.2 Critical Test Cases

  1. Boot twice: One supervisor/service tree; no duplicate migration or enqueue.
  2. Child crash: runit restarts it and restart evidence increments.
  3. Supervisor/tree death: Durable queue remains; health reports down until restart trigger.
  4. Expired lease: New worker safely reclaims it.
  5. Remote accepted, local unknown: Same idempotency key returns prior receipt or enters reconciliation.
  6. Poison job: Reaches dead letter after exact bounded policy.
  7. Low battery/offline: Optional work pauses without losing intake or spinning.
  8. Log pressure: Rotation/headroom guard prevents private-storage exhaustion.

6.3 Test Data

job-local-1       -> deterministic success
job-local-2       -> deterministic success
job-remote-idem   -> receiver stores one receipt per stable key
job-transient     -> fail twice, then succeed after network returns
job-poison        -> permanent malformed payload or bounded repeated failure

kill points       -> before lease | after lease | after remote accept |
                     after result commit | after acknowledgement

7. Common Pitfalls & Debugging

7.1 Frequent Mistakes

Pitfall Symptom Solution
Supervisor never started sv-enable succeeds but service stays down Verify runsvdir before child configuration
Worker daemonizes Supervisor loops while hidden process exists Keep worker foreground
Interactive environment assumed Boot/service cannot find command/config Construct minimal absolute Termux environment
Ack before result commit Job disappears after death Commit result/state before success/ack
New key on retry Remote duplicate effect Persist stable idempotency identity
Lease never expires Dead worker strands job Bounded lease and startup recovery
Infinite retry/crash loop Battery and logs drain Typed cap, jitter, circuit/degraded state, DLQ
Wake lock treated as immortality Service still dies or network pauses Design durable recovery and narrow wake-lock use

7.2 Debugging Strategies

  • Walk outside-in: Android/Boot evidence -> Termux app running -> runsvdir -> runsv -> worker -> heartbeat -> queue -> external dependency.
  • Compare service logs with queue attempt rows under one run/correlation ID.
  • Query leases and expiry before manually resetting anything; destructive cleanup can erase the evidence.
  • Use controlled test jobs and remote receipts for kill tests, never an irreversible real-world action.
  • Record battery/network policy decisions as explicit health reasons.

7.3 Performance Traps

Busy polling, short leases with frequent renewal, excessive concurrency, repeated process startup, verbose logging, and immediate retry all consume energy. Prefer event/timeout waits, limited workers, lease duration derived from action deadline, batched housekeeping, and capped retention. Measure queue latency separately from CPU-awake time.


8. Extensions & Challenges

8.1 Beginner Extensions

  • Add a widget showing service layers, queue depth, and last recovery.
  • Add an explicit dead-letter inspect/requeue command with dry-run.

8.2 Intermediate Extensions

  • Add schema migrations that are idempotent across interrupted boot.
  • Add a circuit breaker for one failing external dependency.
  • Add notification escalation when oldest queue age crosses a policy threshold.

8.3 Advanced Extensions

  • Add multiple action classes with separate concurrency/energy budgets.
  • Add a reconciliation worker for targets without idempotent writes.
  • Compare runit supervision with a conventional Android foreground service and document authority/lifecycle differences.

9. Real-World Connections

9.1 Industry Applications

  • Job queues: Visibility timeouts/leases recover abandoned work.
  • Edge agents: Local durability survives intermittent power and network.
  • Message relays: Stable identities and dead letters bound at-least-once delivery.
  • Service managers: Foreground processes expose true state to supervisors.
  • Mobile sync: Power policy separates accepted local work from deferred remote work.
  • termux-services: GitHub - runit integration and official service/log workflow.
  • Termux:Boot: GitHub - Classic-track boot trigger and ordered script directory.
  • runit: smarden.org/runit - Supervision model and tools.
  • SQLite: sqlite.org - Transactional queue state on one device.

9.3 Interview Relevance

You will be prepared to discuss supervision, foreground processes, leases, visibility timeouts, idempotency, ambiguous side effects, dead letters, backoff, crash loops, health semantics, and mobile background constraints.


10. Resources

10.1 Essential Reading

  • Official termux-services README - Enabling, starting, inspecting, and logging runit services.
  • Official Termux:Boot README - Setup, ordered boot scripts, and wake-lock example context.
  • Android Doze and App Standby documentation - Platform restrictions that supervision cannot remove.
  • “The Linux Programming Interface” by Michael Kerrisk, Chapters 20-26 and 36-37 - Signals, children, daemons, limits.
  • “Designing Data-Intensive Applications” by Martin Kleppmann, Chapter 8 - Distributed uncertainty and fault reasoning.

10.2 Video Resources

  • Android Developers sessions on background execution and power management, checked against current documentation.
  • Process-supervision talks explaining foreground services and failure ownership; translate server assumptions explicitly to Termux.

10.3 Tools & Documentation

  • Project 5: Supplies the offline outbox, retry taxonomy, and network policy.
  • Project 7: Supplies durable Tasker event intake and operation IDs.
  • Project 6: Supplies SSH health/maintenance but deliberately defers persistence here.
  • Next - Project 9: Adds a native probe that can become one bounded worker operation.

11. Self-Assessment Checklist

11.1 Understanding

  • I can explain desired state, supervisor state, process state, and work state separately.
  • I can explain what runit can and cannot recover.
  • I can trace every queue kill point and resulting replay.
  • I can distinguish retry, capability wait, reconciliation, permanent failure, and dead letter.
  • I understand why wake lock and Boot do not guarantee uptime.

11.2 Implementation

  • Boot startup is short, ordered, idempotent, and noninteractive.
  • The worker remains foreground and has bounded action deadlines.
  • Leases, attempts, results, retry, and acknowledgement are transactional.
  • Stable idempotency or reconciliation covers ambiguous effects.
  • Crash loops, poison work, logging, storage, retry, and wake lock are bounded.
  • Full destruction/reboot/offline/low-battery acceptance evidence passes.

11.3 Growth

  • I can identify one reliability claim that the evidence does not support.
  • I documented observed OEM/Android behavior without generalizing it universally.
  • I can explain the recovery matrix and its limitations in an interview.

12. Submission / Completion Criteria

Minimum Viable Completion

  • runit supervises one foreground worker with visible logs and health.
  • Durable leases recover one intentionally killed job.
  • Boot/manual startup is idempotent and does not duplicate service trees.

Full Completion

  • All minimum criteria plus stable idempotency/reconciliation, bounded retry/dead letters/crash loops, power/wake-lock/log policies, process-tree/reboot recovery, and the complete Real World Outcome run.

Excellence (Going Above & Beyond)

  • Publish a formal recovery matrix with durable rows and receipts at every kill point.
  • Measure recovery latency, CPU-awake time, retry count, and queue age across two Android/OEM configurations.
  • Compare the Termux design with a native Android foreground service and clearly separate observed behavior from guaranteed platform contracts.

This guide was generated from TERMUX_ANDROID_APPS_TOOLS_MASTERY.md. For the complete learning path, see the expanded project index.