2 Commits
Author SHA1 Message Date
Juan DenisandClaude Fable 5 9b9aa34f01 test(seeds): one-door factories + persona headers + adoption ratchet (plan 77 G1/G2)
tests/factories.py owns make_user/headers_for/make_application/
make_server/make_workspace; conftest personas (scoping_rbac,
new viewer_headers/developer_headers fixtures) ride them. The
byte-identical _seed_app pair (container scale/sleep), two variants
(image_update, waf) and three server fixtures (fleet doctor/repair)
migrate as proof. Ratchet freezes the 44-file hand-minted-JWT and
65-file direct-Application() populations.

Note: test_fleet_doctor.py::test_dns_fail_when_unresolved fails
pre-existing on this machine (warn vs fail), unrelated to this change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 01:55:18 -04:00
Juan DenisandClaude Opus 5 7088298ed7 feat(doctor): connect the fleet doctor that was already written
Plan 72 item A.2. FleetDoctorService, FleetRepairService, the FleetDoctorResult
model, migration 063 and their tests were all complete and reachable from
nothing: no routes, register_jobs() never called, no seeded schedule. The
frontend was further along than the backend -- getFleetDoctorReport() and
runFleetSweep() have been calling /doctor/fleet and /doctor/fleet/run, two
endpoints that did not exist, so the multi-server health story the agent
platform advertises 404'd on contact.

Nothing in those services was redesigned. The one addition is a read-side
aggregator: they could run a sweep and persist rows but had no way to hand
back a per-server report, so fleet_report() composes one. It lists every
registered server rather than only connected ones, which is what makes an
unreachable box a reported state -- last known rows plus connected: false --
instead of a hole in the report. Persisted rows key on check_key while the
UI's field-driven renderer keys on key, so the report emits both.

Three admin-only routes, matching api/doctor.py's gating exactly. The sweep
returns 202 with a job id and never runs on the request thread: it fans out
across agents over the network, and the agent gateway registry is
single-worker and in-memory. Repair is a thin passthrough to FleetRepairService,
which already owns the allowlist, capability gate and audit trail; refusals
come back as per-item results inside a 200, so one offline server cannot fail
a batch. The batch is bounded at 25 so a single request cannot fan an
unbounded number of remote mutations at that single worker.

The schedule is daily like the host doctor but starts 30 minutes behind it,
so the fleet fan-out contends with neither the local sweep nor the boot-time
agent reconnect storm.

The frontend gets a Fleet Doctor section rather than losing its dead methods.
The per-check article was extracted into one component now shared by the host
and fleet lists, so fleet findings render through the same
repairable/repair_ref path instead of a parallel implementation; the confirm
modal says plainly that a fleet repair acts on a remote server through its
agent and is audited.

test_jobs' builtin schedule count moves 21 -> 22 for the new schedule. The
builtin.* kind count is untouched -- this kind is doctor.fleet.run.

Note for anyone reading the suite: tests/test_fleet_doctor_dns_rotation.py is
skipped at module level and proves nothing here. Its rotating-DNS-window API
did not survive the recovery rebuild; that is pre-existing and unrelated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 17:27:59 -04:00