Server-side zero-touch per-site enrollment (Phase A: backend + DB only;
agent-side machine_uid derivation is Phase B, server treats it as opaque).
Migration 010_spec016_enrollment.sql:
- connect_sites: relational site anchor (site_code natural key, per-tenant
unique). The spec assumed a sites table existed; it did not (site/company
were free-text columns on connect_machines), so this creates a minimal one.
- site_enrollment_keys: rotatable, Argon2id-hashed cek_ secret + monotonic
version + hex fingerprint + active flag; one-active-per-site partial unique.
- connect_machines: + site_id (FK), + enrollment_state ('active'|'pending')
collision gate, + per-tenant (tenant_id, machine_uid) unique index added
ALONGSIDE the 008 global index (the connect-path upsert_machine ON CONFLICT
arbiter binds to 008 — dropping it would break live reconnect).
- connect_sites.enrollment_policy: reserved (default auto-approve), not enforced.
auth/enrollment_keys.rs: cek_ mint (256-bit, OS CSPRNG), Argon2id hash/verify
(reuses auth::password), and hex fingerprint vN (XXXX) per resolved-decision #3.
db/sites.rs + db/enrollment_keys.rs: runtime sqlx persistence; rotate_key
deactivates+inserts in one tx to hold the one-active-key invariant.
POST /api/enroll (public, api/enroll.rs): site_code+cek_ verify against active
key -> dedup on (tenant, machine_uid) -> new / reuse / site-move / collision.
Collision gate (PROVISIONAL heuristic: online existing row + different hostname)
-> pending, no usable cak_, alert. Mints cak_ via existing agent_keys path in the
exact form relay::validate_agent_api_key expects. Per-(site_code,IP) rate-limit +
lockout (EnrollLimiter). Audit events + [ENROLL] alert markers with
TODO(SPEC-016) #dev-alerts notes.
Admin (JWT) api/sites.rs: POST /api/sites/:id/enrollment-key/rotate (plaintext +
fingerprint once) and GET .../enrollment-key (fingerprint/version, no secret).
Routes wired in main.rs (enroll public, rotation admin). 13 new unit tests;
full server suite 99 passing. cargo check + clippy clean on the host (Windows)
target — Linux cross-target not installed here; server crate is platform-neutral
Rust. No sqlx offline cache needed (codebase uses runtime queries, no query!).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Flip both CI gates from informational to hard-fail (SPEC-001 quality gates):
- clippy: `-- -D warnings` on the server crate. Cleared the debt via clippy --fix
(unused imports/style), targeted #[allow(dead_code)] on native-remote-control
future API, and #[allow(clippy::too_many_arguments)] on 3 protocol-mirroring fns.
- cargo audit: hard-fail with documented per-ID --ignore flags (rsa RUSTSEC-2023-0071
unfixable/unreachable in active tree; gtk-rs + glib Linux-only tray backend not
compiled into the Windows agent; proc-macro-error build-time). New advisories fail.
- Move [profile.release] to the workspace root (it was silently ignored in the server
member), activating lto/codegen-units/strip.
No behavioral changes. Reviewed and gates verified passing on the build host.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First run of the build-and-test CI gate (cargo fmt --all -- --check) surfaced
pre-existing formatting drift across the agent and server crates. Apply rustfmt
across the workspace so the codebase meets its own CI gate. Pure formatting; no
logic changes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Features:
- Agent checks for updates periodically (hourly) during idle
- Admin can trigger immediate updates via dashboard "Update Agent" button
- Silent updates with in-place binary replacement (no reboot required)
- SHA-256 checksum verification before installation
- Semantic version comparison
Server changes:
- New releases table for tracking available versions
- GET /api/version endpoint for agent polling (unauthenticated)
- POST /api/machines/:id/update endpoint for admin push updates
- Release management API (/api/releases CRUD)
- Track agent_version in machine status
Agent changes:
- New update.rs module with download/verify/install/restart logic
- Handle ADMIN_UPDATE WebSocket command for push updates
- --post-update flag for cleanup after successful update
- Periodic update check in idle loop (persistent agents only)
- agent_version included in AgentStatus messages
Dashboard changes:
- Version display in machine detail panel
- "Update Agent" button for each connected machine
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add connect_machines, connect_sessions, connect_session_events, connect_support_codes tables
- Implement db module with connection pooling (sqlx)
- Add machine persistence across server restarts
- Add audit logging for session/viewer events
- Support codes now persisted to database
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>