From c6abf297247c9c93d51a482f846c9066a65a0fe0 Mon Sep 17 00:00:00 2001 From: Elie Bursztein Date: Mon, 28 Sep 2026 01:56:47 +0000 Subject: [PATCH] test(service): the asset readiness invariant includes files and reconcile Release run 36361892126 failed only on test_assets_reports_ready_when_all_present: ready=False while every boot asset was present, because the service's startup reconcile was still fetching the profile's software-inventory.json (downloading=True). That is the product contract: ready covers the profile's files as well as its assets, and an in-flight reconcile reports ready=False until it publishes a fresh snapshot (profile_status_cache::refresh_reconcile_fields). The test read only `assets`, so it raced the startup reconcile. --- tests/capsem-service/test_svc_install.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/tests/capsem-service/test_svc_install.py b/tests/capsem-service/test_svc_install.py index c1ad31886..39bffd494 100644 --- a/tests/capsem-service/test_svc_install.py +++ b/tests/capsem-service/test_svc_install.py @@ -73,21 +73,30 @@ def test_assets_lists_three_expected_artifacts(self, client): assert asset["status"] in ("present", "missing") def test_assets_reports_ready_when_all_present(self, client): - """ready=true iff every listed asset has status=present. + """ready=true iff every asset and profile file is present and no + reconcile is repairing them. Test binaries are spawned with --assets-dir pointing at the real repo assets, so in a dev environment this should be ready=true. If assets haven't been built yet, we accept ready=false but still - verify the invariant. + verify the invariant. The service's startup reconcile may still be + fetching a profile file (software-inventory.json) when this asks; + while it runs, ready is deliberately false. The invariant used to + read only `assets`, and failed a release run on exactly that. """ resp = client.get("/profiles/code/assets/status") assert resp is not None if resp.get("error"): # No asset manifest -- skip the invariant but keep shape assertion. return - all_present = all(a["status"] == "present" for a in resp["assets"]) - assert resp["ready"] == all_present, ( - f"ready={resp['ready']} but all_present={all_present}: {resp}" + all_present = all( + entry["status"] == "present" + for entry in [*resp["assets"], *resp.get("files", [])] + ) + expected = all_present and not resp["downloading"] + assert resp["ready"] == expected, ( + f"ready={resp['ready']} but all_present={all_present} " + f"downloading={resp['downloading']}: {resp}" ) def test_assets_ensure_returns_status_shape(self, client):