fix(fleet): read existing fleet from fleet_deckies, not State["deployment"] (BUG-2)
The web deploy collision-guard read the existing fleet from the DB State["deployment"] key, while the UI/get_deckies() read decnet-state.json. A fleet established via CLI/seed lands in neither path the guard consulted, so existing_deckies was empty, the additive guard ran blind, and the reconciler tore the running fleet down to the single submitted decky (BUG-2: silent fleet wipe, HTTP 202, no warning). Converge both reads on fleet_deckies — the engine-mirrored table written on every deploy/teardown (CLI and web), which fleet/reconciler.py already documents as the store the orchestrator, dashboard, and REST API see. Each row's decky_config column is a full DeckyConfig dump, so it rehydrates losslessly into the collision-guard input. The handler also commits the intended fleet to fleet_deckies synchronously so rapid sequential deploys read a current fleet and the dashboard observes the new shape immediately. State["deployment"] is retained for now — the mutate handlers and the mutator engine still coordinate through it; consolidating them is tracked in development/ADR-001-FLEET-SOURCE-OF-TRUTH.md (open question 7). Tests seed fleet_deckies directly (also modelling the CLI-seeded scenario) rather than chaining real deploys through the skipped contract-test path.
This commit is contained in:
@@ -211,6 +211,47 @@ def mock_state_file(patch_state_file: Path):
|
||||
patch_state_file.write_text(json.dumps(_test_state))
|
||||
yield _test_state
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def mock_fleet_deckies():
|
||||
"""Seed fleet_deckies with two deckies — the store get_deckies() reads
|
||||
under the Option-D source-of-truth model (development/ADR-001-...md).
|
||||
Mirrors the data mock_state_file used to put in decnet-state.json."""
|
||||
from decnet.config import DeckyConfig
|
||||
from decnet.web.db.models import LOCAL_HOST_SENTINEL
|
||||
from decnet.web.dependencies import repo
|
||||
|
||||
async def _clear() -> None:
|
||||
for row in await repo.list_fleet_deckies():
|
||||
await repo.delete_fleet_decky(
|
||||
host_uuid=row.get("host_uuid") or LOCAL_HOST_SENTINEL,
|
||||
name=row["name"],
|
||||
)
|
||||
|
||||
specs = [
|
||||
("test-decky-1", "192.168.1.10", ["ssh"], "debian", "test-host-1",
|
||||
{"ssh": {"banner": "SSH-2.0-OpenSSH_8.9"}}, "deaddeck"),
|
||||
("test-decky-2", "192.168.1.11", ["http"], "ubuntu", "test-host-2",
|
||||
{}, None),
|
||||
]
|
||||
await _clear()
|
||||
for name, ip, services, distro, hostname, svc_cfg, arche in specs:
|
||||
cfg = DeckyConfig(
|
||||
name=name, ip=ip, services=services, distro=distro,
|
||||
base_image=distro, hostname=hostname,
|
||||
service_config=svc_cfg, archetype=arche,
|
||||
)
|
||||
await repo.upsert_fleet_decky({
|
||||
"host_uuid": LOCAL_HOST_SENTINEL,
|
||||
"name": name,
|
||||
"services": services,
|
||||
"decky_config": cfg.model_dump(mode="json"),
|
||||
"decky_ip": ip,
|
||||
"state": "running",
|
||||
})
|
||||
yield
|
||||
await _clear()
|
||||
|
||||
# Share fuzz settings across API tests
|
||||
# FUZZ_EXAMPLES: keep low for dev speed; bump via HYPOTHESIS_MAX_EXAMPLES env var in CI
|
||||
_FUZZ_EXAMPLES = int(_os.environ.get("HYPOTHESIS_MAX_EXAMPLES", "10"))
|
||||
|
||||
Reference in New Issue
Block a user