6.0 KiB
6.0 KiB
1. Config and dependencies
- 1.1 Add
fastapianduvicorn[standard]as explicit direct dependencies inapps/device-host-agent/pyproject.toml(versions matching those already pinned inuv.lock) - 1.2 Add
console_bind_host,console_port,console_allow_non_loopback,console_session_ttl_seconds, andconsole_history_limitfields toHostAgentConfiginhost_agent/config.py, plus matchingHOST_AGENT_CONSOLE_*env vars except an enable flag inload_host_agent_config, reusing the existing_positive_float/_positive_intvalidators - 1.3 Add validation that raises
HostAgentConfigurationErrorwhenconsole_bind_hostis non-loopback andconsole_allow_non_loopbackis not set - 1.4 Add unit tests in
apps/device-host-agent/tests/test_config.pyfor defaults, env var parsing, and the non-loopback-without-opt-in rejection
2. Shared device-registration helper
- 2.1 Extract the device-add sequence (
DeviceConfigStore.add/set_cloud_device_id+ optionalenrollment_client.enroll_device+manager.register_device) currently inlined inhost_agent/app.py::_configured_device_managerinto a small shared function usable by both startup and the console - 2.2 Add a matching shared function for device removal (
DeviceConfigStore.remove+manager.unregister_device) - 2.3 Update
_configured_device_managerto use the extracted add helper; confirm existingtest_app.pystartup tests still pass unchanged
3. Local history store
- 3.1 Add a new
host_agent/history.pymodule with aConsoleHistoryStorebacked by a small SQLite file (e.g.tasks/host_console_history.sqlite3), supportingrecord_assignment(...),record_heartbeat(...), andlist_recent(limit), pruning beyondconsole_history_limiton write - 3.2 Add an optional recorder hook to
AssignmentProcessor.process(host_agent/processor.py) invoked after a terminal result is reported, no-op when no recorder is configured - 3.3 Add an optional recorder hook to
HeartbeatSynchronizer.sync_once(host_agent/heartbeat.py) invoked after each successful sync, no-op when no recorder is configured - 3.4 Unit tests for
ConsoleHistoryStore(write, prune-on-overflow, ordering) and for the processor/heartbeat recorder hooks firing with the expected data and being skipped when absent
4. Session and authentication
- 4.1 Add
host_agent/web/auth.pywith an in-memory session store (opaque token → session state with expiry), login verification againstLocalAccountStore, and CSRF token issuance/validation bound to the session - 4.2 Implement session cookie handling (
HttpOnly,SameSite=Strict,Securewhen bind host is non-loopback) and sliding expiry perconsole_session_ttl_seconds - 4.3 Implement an auth dependency/middleware that redirects unauthenticated requests to
/loginand rejects mutating requests lacking a valid CSRF token - 4.4 Unit tests: successful login, wrong password, no-account-yet state, session expiry, CSRF rejection on a mutating route, redirect-to-login for an unauthenticated GET
5. Console pages and routes
- 5.1 Add
host_agent/web/app.pybuilding a FastAPI sub-application with hand-written HTML responses (f-string templates + a sharedescape()helper for every interpolated value) for:/login,/(status dashboard),/devices,/account,/history - 5.2 Implement
/login(GET form, POST verify+establish session) per spec scenarios, including the "no local account exists" state - 5.3 Implement the status dashboard: last heartbeat outcome/time, enrollment/identity state, device list with status, current assignment/execution state, sanitized effective config (no token/password rendered); add a small JSON status-fragment endpoint polled via inline
fetch()for refresh without full reload - 5.4 Implement
/devices: list, add, edit, remove forms wired to the section-2 shared helpers, taking effect on the liveDeviceManagerimmediately - 5.5 Implement
/account: change-password form requiring current password re-entry, callingLocalAccountStore.create(or an equivalent update path) only after verifying the current credential - 5.6 Implement
/history: read-only table of recent assignment/heartbeat entries fromConsoleHistoryStore - 5.7 Implement
/logout(CSRF-protected POST) invalidating the session
6. Lifecycle wiring
- 6.1 In
host_agent/app.py::create_application, always construct the console app, session store, andConsoleHistoryStore, and wire the recorder hooks from section 3 into the constructedAssignmentProcessor/HeartbeatSynchronizer - 6.2 In
HostAgentApplication.run_async, start auvicorn.Servertask (bound toconsole_bind_host/console_port,install_signal_handlers=False) alongside the heartbeat task and stop it in the existingfinallyshutdown sequence - 6.3 Integration test exercising the full lifecycle: process starts, console responds on the configured loopback port, process shuts down cleanly and stops the console server
- 6.4 Integration test confirming that the Console is constructed by default and existing
test_app.py/test_e2e.pybehavior is unaffected
7. Documentation
- 7.1 Document the
HOST_AGENT_CONSOLE_*environment variables excluding an enable flag, mandatory loopback-bound startup, and the SSH port-forward recommendation for remote access indocs/CLOUD_DEPLOYMENT.md - 7.2 Add a short section to
docs/MACOS_IPHONE_SETUP.mddescribing mandatory Console startup on an edge machine and what it shows
8. Validation
- 8.1 Run
uv run --package device-host-agent pytest(full package suite) and the root non-integration suite; confirm no regressions - 8.2 Run Ruff check/format and
python -m compileallover the changed files - 8.3 Manually verify in a browser: login, status dashboard auto-refresh, add/edit/remove a device, change password, view history, logout, and confirm the console refuses to bind non-loopback without the opt-in flag
- 8.4 Run
openspec validate host-agent-local-console --strictand confirm it passes