Skip to content

Testing plan: one the-loop start for every service the config enables ​

Derived from requirements.md and design.md, before tasks.md. Authored at test-planning, completed at verification — the Results column and the activities checklist are filled in there.

This file is executable content: it names commands an agent runs. All of them are this repository's own test/lint commands against temp directories; no credentials.

What this work item has to prove ​

  1. Composition is faithful to the flags. start starts exactly the enabled services, stop stops running ones regardless of flags, status's exit code means "everything enabled is up". Unit-level with fakes, plus one real-process integration pass.
  2. Nothing the poll command did is lost. The poller still runs (foreground, once, detached-by-spawn), still locks, still heartbeats, still hot-reloads — driven through its new entry points. The old tests are re-pointed, not deleted: each scenario keeps its Gherkin docstring and asserts the same behaviour via daemon_entry / poller.daemon / the-loop start.
  3. The restart API scheduls a real restart without dying mid-answer. The route answers immediately with a pid; the spawned argv is fixed; the contract file matches the app.
  4. Fail-closed holds. Disabled service ⇒ no auto-start; MCP disabled ⇒ /mcp 404; unloadable config ⇒ start refuses.

Test-type matrix ​

TypeIn scope?What / why
Unityescore.lifecycle plan/start/stop/status/schedule with fakes; service_config flag parsing; ensure_service refusal; daemon_entry arg handling
Integrationyesre-pointed poller daemon tests (real subprocesses, temp state root); the-loop start/stop/status/restart end-to-end with service; /api/v1/restart over a live app; /mcp 404 when disabled
ContractyesOpenAPI parity test already enforces app ↔ the-loop.v1.yaml; restart route added to both
Schema/docs parityyesexisting suites: schema copies byte-identical; docs P1–P5 over the new/removed command pages and new keys
e2e (PDLC)n/athe pdlc e2e suite exercises graph walking, untouched here
UI/visualyes (added on PR #229 review)the dashboard restart: client method (live+demo), Settings Service card, config-editor "Restart now" — vitest component tests, oxlint, tsc, vite build
Performancen/ano hot path touched; start-up waits are bounded and configurable in tests
Security/abuseyesrestart body rejects non-boolean shapes (pydantic); fixed-argv assertion on the spawned process; MCP-disabled 404; auto-start refusal when service.enabled: false
Accessibilityn/athe two added controls are native buttons/checkbox+label, covered by the existing component-test conventions; no new interaction pattern
Migrationn/akeys added, none removed; migrate-config untouched — asserted by the existing migration suite staying green
Manualyesone scripted smoke: the-loop start → status → restart → stop in a temp HOME, transcript to evidence

Verification environment ​

This repository's checkout only: uv workspace in cli/, pytest, ruff, pyright, markdownlint. Real subprocesses bind loopback ports chosen free-at-test-time (existing _free_port() pattern). No network beyond loopback; --with-upgrade is tested at the plan level (dry-run) — executing a real upgrade would mutate the environment.

Activities ​

Completed at verification; raw output in evidence/verification.md.

#ActivityCommandResult
T1✅ New unit tests: core.lifecycleuv run pytest tests/test_core_lifecycle.py10 passed
T2✅ New unit/integration: lifecycle commands (start/stop/status/restart rendering, exit codes)uv run pytest tests/test_lifecycle_cmd.py7 passed
T3✅ Re-pointed poller daemon tests (foreground, --once, lock, status, heartbeat, ttyd wiring)uv run pytest tests/test_poll_daemon_integration.py tests/test_poll_command.py tests/test_poll_status.py tests/test_poll_heartbeat.py37 passed (one contention-induced flake while the full suite ran concurrently, 3× re-runs green — recorded in evidence)
T4✅ Restart API + MCP flag integrationuv run pytest tests/test_service_lifecycle_integration.py tests/test_mcp_integration.py tests/test_api_routers_integration.py19 passed
T5✅ Contract parity (restart route in the OpenAPI file)uv run pytest tests/test_api_contract_parity.py tests/test_config_schema_parity.py tests/test_docs_parity.py tests/test_configschema.py29 passed (run combined with T6)
T6✅ Schema copies + docs parity + config schema validitycombined into T5's rungreen: copies byte-identical, docs P1–P5
T7✅ Full suitecd cli && uv run pytest2034 passed, 1 skipped (pre-existing skip); three full runs, all green
T8✅ Lint / format / typesmake lint format-check typecheckruff clean, 212 files formatted, pyright 0 errors
T9✅ Markdown lintpart of make lint (markdownlint-cli2)658 files, 0 errors
T10✅ Config validation (template with new keys)uv run python scripts/validate_config.py (make validate)all 7 configs VALID
T11✅ Manual smoke (temp HOME): start → status → restart → stopscripted, transcript committedfull lifecycle against a real service; exit codes as specified
T12✅ Reference sweep: no live doc/code names the-loop poll outside historygrep -rn "the-loop poll" … excluding specs/decisions1 intended survivor (the new module's own docstring describing the move); 4 stragglers found and fixed
T13✅ (added, PR #229 review) The fold + the dashboard restartfull CLI suite again after removing gh-webhook/service (receiver via webhook/daemon.py); UI vitest + oxlint + tsc + vite build with the restart client/card/"Restart now"CLI: 2034 passed, 1 skipped; UI: 91 passed (2 new), lint/typecheck clean, build ok
T14✅ (added, PR #229 review round 2 / issue-231) Single-process modeunit: 5 hosted cases in tests/test_core_lifecycle.py (host skips spawn + awaits locks; hostIngresses: false keeps standalone; disabled service never hosts; hosted fail when the service is down; stop-via-service with real RunLocks); integration: tests/test_hosted_ingress_integration.py — real the-loop start boots one pid holding all three locks, hosted receiver answers /health on its port, status --format json rows say hosted: true, stop releases everything; a standalone daemon's lock is skipped, not stolen; then the full suiteunit 15 passed; integration 2 passed; full suite 2041 passed, 1 skipped

Released under the MIT License.