Active user attribute set, latest activities, guarded changes to used values, artefact links, durations and averages; irreversible showcase step
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 3s
Work Records / validate (push) Has been cancelled

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 295952@bnt-lap001
Assistant-Session: e93f64ad-516c-46eb-9666-aad8d300c477
This commit is contained in:
tegwick 2026-09-29 15:50:56 +02:00
parent 6716cee921
commit 8d62c1fbe8
22 changed files with 1025 additions and 35 deletions

View file

@ -56,13 +56,49 @@ def _coerce(run: Run, dp_id: str, value: str) -> tuple[str | None, str | None]:
return value, None
def set_datapoint(run: Run, dp_id: str, value: str, how: str = "manual") -> str:
"""Give a datapoint a value. Invalid input leaves it in state 'err' (never stores it)."""
def datapoint_users(run: Run, dp_id: str) -> list[str]:
"""Steps that have already started and use this datapoint: they worked with its current value."""
return [sid for sid, s in flow(run).steps.items()
if dp_id in s.get("uses", []) and run.data["steps"][sid]["state"] in ("running", "done", "failed", "pushed")]
def changes_needing_override(run: Run, changes: dict[str, str]) -> dict[str, list[str]]:
"""Of proposed {datapoint: new value}, those that steps already used and that really change the value."""
out = {}
for dp, v in changes.items():
rec = run.data["datapoints"].get(dp)
if rec is None or rec.get("value") is None or str(v) == str(rec["value"]):
continue
users = datapoint_users(run, dp)
if users:
out[dp] = users
return out
def set_datapoint(run: Run, dp_id: str, value: str, how: str = "manual",
override_reason: str = "", acknowledge_risk: bool = False) -> str:
"""Give a datapoint a value. Invalid input leaves it in state 'err' (never stores it).
Changing a value that steps have already used is a deviation: it needs the operator's override
(reason and acknowledged risk), only in manual mode, and is recorded as 'changed-after-use'.
"""
_writable(run)
if dp_id not in flow(run).datapoints:
raise RunError(f"no such datapoint: {dp_id}")
clean, err = _coerce(run, dp_id, value)
rec = run.data["datapoints"][dp_id]
users = datapoint_users(run, dp_id) if how != "initial" else []
if users and rec.get("value") is not None and str(value) != str(rec["value"]):
risk = "Steps that already ran used the previous value, so their results may no longer match this one."
if run.data["mode"] != "manual":
raise RunError(f"{dp_id} was already used by {', '.join(users)}; take over (manual mode) to change it")
if not override_reason.strip():
raise RunError(f"{dp_id} was already used by {', '.join(users)}. Risk: {risk} "
"To change it give a reason and acknowledge the risk; this is recorded as a deviation.")
if not acknowledge_risk:
raise RunError("changing a value that steps already used needs the operator to acknowledge the risk")
_deviate(run, "changed-after-use", dp_id, [f"{s} already used {dp_id} = {rec['value']!r}" for s in users],
override_reason, risk, True)
clean, err = _coerce(run, dp_id, value)
if err:
rec.update(state="err", error=err, at=_now())
rec.pop("value", None)