fix(update): the receipt's build stage fails only for a build follow-up (C3)
Both completion paths marked the build stage failed whenever ANY tail follow-up was owed, so a run whose only debt was config_migration (or maintenance / dependencies) reported a failed build. record_build_stage marks it failed only for launchers/build (what the build stage publishes) or completion (the tail raised before proving them); other owed steps stay their own follow-ups.
This commit is contained in:
@@ -263,7 +263,8 @@ def _complete_selected(request: dict) -> bool:
|
||||
from hermes_cli import main, update_cmd, update_cmd_config
|
||||
from hermes_cli.source_completion import complete_source_checkout
|
||||
from hermes_cli.update_inventory import RuntimeRecord, UpdatePlan
|
||||
from hermes_cli.update_receipt import TAIL_FOLLOWUPS, record_followup, record_skip, record_stage
|
||||
from hermes_cli.update_receipt import (
|
||||
TAIL_FOLLOWUPS, record_build_stage, record_followup, record_skip, record_stage)
|
||||
|
||||
root = Path(request["source"])
|
||||
main.PROJECT_ROOT = root
|
||||
@@ -294,7 +295,7 @@ def _complete_selected(request: dict) -> bool:
|
||||
record_followup("completion", reason)
|
||||
followups.append(("completion", reason))
|
||||
tail_owed = any(step in TAIL_FOLLOWUPS for step, _ in followups)
|
||||
record_stage("build", "failed" if tail_owed else "success")
|
||||
record_build_stage(followups)
|
||||
if not tail_owed:
|
||||
from hermes_cli.venv_sync import clear_completion
|
||||
clear_completion(root)
|
||||
|
||||
@@ -22,7 +22,7 @@ def finish_update(*, root, assume_yes, gateway_mode, pre_update_snapshot_id,
|
||||
_restart_gateway_fleet_after_update, _verify_fleet_after_update,
|
||||
_write_gateway_update_exit_code, _resume_windows_gateways_and_merge_outcome,
|
||||
)
|
||||
from hermes_cli.update_receipt import TAIL_FOLLOWUPS, record_followup, record_stage
|
||||
from hermes_cli.update_receipt import TAIL_FOLLOWUPS, record_build_stage, record_followup
|
||||
|
||||
owed = followups if followups is not None else []
|
||||
runtime_safe = False
|
||||
@@ -38,7 +38,7 @@ def finish_update(*, root, assume_yes, gateway_mode, pre_update_snapshot_id,
|
||||
record_followup("maintenance", reason)
|
||||
owed.append(("maintenance", reason))
|
||||
tail_owed = any(step in TAIL_FOLLOWUPS for step, _ in owed)
|
||||
record_stage("build", "failed" if tail_owed else "success")
|
||||
record_build_stage(owed)
|
||||
if not tail_owed:
|
||||
from hermes_cli.source_stamp import write_source_stamp
|
||||
from hermes_cli.venv_sync import clear_completion
|
||||
|
||||
@@ -317,6 +317,17 @@ def persist_running_receipt() -> None:
|
||||
#: Follow-up steps whose failure leaves the source-update tail owed (``source-completion-pending``).
|
||||
TAIL_FOLLOWUPS = frozenset({"dependencies", "launchers", "build", "maintenance", "config_migration", "completion"})
|
||||
|
||||
#: Follow-ups that mean the BUILD stage did not succeed (C3: the receipt names what actually failed):
|
||||
#: the products or the launchers the build publishes failed, or the tail raised before proving them.
|
||||
#: Owed dependencies, config migration or maintenance keep the tail armed but say nothing about the
|
||||
#: build; they are reported as their own follow-ups.
|
||||
BUILD_FOLLOWUPS = frozenset({"launchers", "build", "completion"})
|
||||
|
||||
|
||||
def record_build_stage(followups) -> None:
|
||||
"""Mark the build stage from the owed ``(step, reason)`` follow-ups: failed only for a build one."""
|
||||
record_stage("build", "failed" if any(step in BUILD_FOLLOWUPS for step, _ in followups) else "success")
|
||||
|
||||
|
||||
def record_followup(step: str, reason: str, *, retry: str = "the next launch or `hermes update` retries it") -> None:
|
||||
"""A post-commit step failed: print ⚠, keep the run a success, and say what is still owed.
|
||||
|
||||
@@ -155,6 +155,7 @@ def transition(tmp_path):
|
||||
"_current = contextvars.ContextVar('receipt', default=None)\n"
|
||||
"class UpdateReceipt: pass\n"
|
||||
"def record_stage(*args, **kwargs): pass\n"
|
||||
"def record_build_stage(*args, **kwargs): pass\n"
|
||||
"def record_skip(*args, **kwargs): pass\n"
|
||||
"TAIL_FOLLOWUPS = frozenset({'dependencies', 'launchers', 'build', 'maintenance', 'config_migration', 'completion'})\n"
|
||||
"def record_followup(step, reason, **kwargs):\n"
|
||||
|
||||
@@ -428,6 +428,8 @@ def test_real_compiler_failure_is_owed_build_and_completion_continues(completion
|
||||
receipt = json.loads((home / "logs/update_receipts/latest.json").read_text())
|
||||
assert receipt["outcome"] == "success"
|
||||
assert [row["step"] for row in receipt["followups"]] == ["build"]
|
||||
# Only a build follow-up marks the build stage failed (an owed config/dependency step does not).
|
||||
assert {mark["name"]: mark["outcome"] for mark in receipt["stages"]}.get("build") == "failed"
|
||||
assert receipt["update_id"] == request["update_id"]
|
||||
assert receipt["steps"][0] == request["receipt"]["steps"][0]
|
||||
assert receipt["pm_venv_rebuild"] == request["pm_receipt"]["venv_rebuild"]
|
||||
|
||||
@@ -87,6 +87,7 @@ _CHILD = textwrap.dedent('''
|
||||
(cell / "summary.json").write_text(json.dumps({
|
||||
"code": code, "outcome": latest.get("outcome"),
|
||||
"followups": [row["step"] for row in latest.get("followups", [])],
|
||||
"stages": {mark["name"]: mark["outcome"] for mark in latest.get("stages", [])},
|
||||
"config_version": yaml.safe_load((home / "config.yaml").read_text())["_config_version"],
|
||||
"latest_config_version": DEFAULT_CONFIG["_config_version"],
|
||||
"pending": completion_pending_path(source).is_file(),
|
||||
@@ -153,6 +154,8 @@ def test_config_format_write_failure_is_owed_not_reported_complete(tmp_path):
|
||||
assert summary["followups"] == ["config_migration"]
|
||||
assert summary["pending"] is True
|
||||
assert summary["stamp"] is False
|
||||
# C3: the receipt names what actually failed. The build succeeded; only the config is owed.
|
||||
assert summary["stages"].get("build") == "success", summary["stages"]
|
||||
|
||||
|
||||
_RECEIPT_CHILD = textwrap.dedent('''
|
||||
|
||||
Reference in New Issue
Block a user