Skip to content

Commit b3b7c07

Browse files
committed
[fix.dirac.σ.D9+] make Program interaction boundaries fail closed
Invalid dependencies and dates must not leave ghost work, missing Program routes must not substitute another project, and keyboard/chart interactions must remain operable across mounted workspaces. Add atomicity, scale, DB cleanup, accessibility, export, layout, and state-isolation coverage because an all-green happy path was hiding real persistence and HCI defects.
1 parent 56ee548 commit b3b7c07

13 files changed

Lines changed: 4765 additions & 3961 deletions

File tree

‎backend/programs/repository.py‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -198,14 +198,9 @@ def record_work_package(self, program_ref: dict, expected_version: int, value: d
198198
"title": item["title"], "lane": item["lane"],
199199
"created_by": who, "created_at": datetime.now(timezone.utc).isoformat(),
200200
"transitions": [], "executions": []}
201-
row["work_items"].append(work_item)
202-
transition = {"ref": _ref("artifact", uuid.uuid4()), "work_item_ref": work_item["ref"],
203-
"from_lane": None, "to_lane": item["lane"],
204-
"reason": "Created in this workflow lane", "transitioned_by": who,
205-
"transitioned_at": datetime.now(timezone.utc).isoformat()}
206-
work_item["transitions"].append(transition); row["work_transitions"].append(transition)
207201
dependency_refs = copy.deepcopy(item["depends_on_refs"])
208202
known_items = {entry["ref"]["id"]: entry for entry in row["work_items"]}
203+
known_items[work_item["ref"]["id"]] = work_item
209204
if any(dependency["id"] == work_item["ref"]["id"] for dependency in dependency_refs):
210205
raise failures.DiracInvalidParameters("A Work Item cannot depend on itself")
211206
if any(dependency["id"] not in known_items for dependency in dependency_refs):
@@ -220,6 +215,13 @@ def reaches_current(candidate_id: str, visited: set[str] | None = None) -> bool:
220215
for ref in (candidate or {}).get("depends_on_refs", []))
221216
if any(reaches_current(dependency["id"]) for dependency in dependency_refs):
222217
raise failures.DiracInvalidParameters("Work Item dependencies must remain acyclic")
218+
if created:
219+
row["work_items"].append(work_item)
220+
transition = {"ref": _ref("artifact", uuid.uuid4()), "work_item_ref": work_item["ref"],
221+
"from_lane": None, "to_lane": item["lane"],
222+
"reason": "Created in this workflow lane", "transitioned_by": who,
223+
"transitioned_at": datetime.now(timezone.utc).isoformat()}
224+
work_item["transitions"].append(transition); row["work_transitions"].append(transition)
223225
current = next((package for package in reversed(row["work_packages"])
224226
if package["work_item_ref"] == work_item["ref"]
225227
and package.get("status") != "superseded"), None)

‎backend/tests/test_artifacts_pg.py‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
pytest.skip('requires isolated PostgreSQL DIRAC_TEST_DSN', allow_module_level=True)
4545
PASS, FAIL = [], []
4646
MARKER = b'dirac-artifact-pg-test-'
47+
RUN_TOKEN = os.urandom(8).hex().encode()
4748
WRITTEN: list[str] = [] # digests to clean up
4849

4950

@@ -93,7 +94,7 @@ def any_method_row() -> str:
9394

9495

9596
def payload(tag: str, size: int = 4096) -> bytes:
96-
body = MARKER + tag.encode() + b'\n' + (bytes(range(256)) * (size // 256))
97+
body = MARKER + RUN_TOKEN + b'-' + tag.encode() + b'\n' + (bytes(range(256)) * (size // 256))
9798
return body
9899

99100

@@ -295,6 +296,18 @@ def cleanup() -> None:
295296
cur.execute("DELETE FROM app.job WHERE worker = 'artifact-pg-test'")
296297

297298

299+
if 'pytest' in sys.modules:
300+
import pytest
301+
302+
@pytest.fixture(scope='module', autouse=True)
303+
def cleanup_pytest_artifacts():
304+
"""The standalone runner already cleans up; pytest must preserve that contract."""
305+
try:
306+
yield
307+
finally:
308+
cleanup()
309+
310+
298311
if __name__ == '__main__':
299312
try:
300313
for name, fn in list(globals().items()):

‎backend/tests/test_persist_counters.py‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,28 @@
2727
PASS, FAIL = [], []
2828

2929

30+
if 'pytest' in sys.modules:
31+
import pytest
32+
33+
@pytest.fixture(scope='module', autouse=True)
34+
def restore_import_only_field_server_state():
35+
"""This module deliberately starts DB state; do not leak it into other tests."""
36+
before = {
37+
'db_ok': fs._db_ok, 'toolkit_ids': dict(fs._toolkit_ids),
38+
'producer_id': fs._producer_id, 'method_ids': dict(fs._method_ids),
39+
'method_versions': dict(fs._method_versions), 'jobs': fs._jobs,
40+
}
41+
try:
42+
yield
43+
finally:
44+
fs._db_ok = before['db_ok']
45+
fs._toolkit_ids = before['toolkit_ids']
46+
fs._producer_id = before['producer_id']
47+
fs._method_ids = before['method_ids']
48+
fs._method_versions = before['method_versions']
49+
fs._jobs = before['jobs']
50+
51+
3052
def check(name, fn):
3153
try:
3254
fn()

‎backend/tests/test_program_aggregate.py‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,45 @@ def test_work_schedule_rejects_reversed_dates(self) -> None:
205205
"progress_percent": 101,
206206
}, ACTOR, "bad-progress")
207207

208+
def test_every_progress_value_round_trips_and_invalid_types_are_atomic(self) -> None:
209+
version = 1
210+
for progress in range(101):
211+
result = self.repo.record_work_package(self.program_ref, version, {
212+
"key": "progress-sweep", "title": "Progress sweep",
213+
"description": "Exercise every persisted percentage",
214+
"progress_percent": progress,
215+
}, ACTOR, f"progress-{progress}")
216+
version = result["program_version"]
217+
self.assertEqual(result["work_item"]["progress_percent"], progress)
218+
self.assertEqual(result["work_package"]["revision"], progress + 1)
219+
for index, bad in enumerate((-1, 101, True, False, 1.5, "42", None)):
220+
before = copy.deepcopy(self.repo.get(self.program_ref)["program"])
221+
with self.assertRaises(failures.DiracInvalidParameters):
222+
self.repo.record_work_package(self.program_ref, version, {
223+
"key": f"bad-progress-{index}", "title": "Invalid progress",
224+
"description": "Must leave no partial Work Item", "progress_percent": bad,
225+
}, ACTOR, f"bad-progress-{index}")
226+
after = self.repo.get(self.program_ref)["program"]
227+
self.assertEqual(after["version"], before["version"])
228+
self.assertEqual(after["counts"], before["counts"])
229+
230+
def test_failed_new_dependency_and_bad_dates_leave_no_ghost_work(self) -> None:
231+
for index, payload in enumerate((
232+
{"depends_on_refs": [{"kind": "work_item", "id": "outside-program"}]},
233+
{"start_on": "2025-02-29", "due_on": "2025-03-01"},
234+
{"start_on": "2026-04-02", "due_on": "2026-04-01"},
235+
)):
236+
before = copy.deepcopy(self.repo.get(self.program_ref)["program"])
237+
with self.assertRaises(failures.DiracInvalidParameters):
238+
self.repo.record_work_package(self.program_ref, before["version"], {
239+
"key": f"atomic-failure-{index}", "title": "Atomic failure",
240+
"description": "The rejected write must not leave a shell", **payload,
241+
}, ACTOR, f"atomic-failure-{index}")
242+
after = self.repo.get(self.program_ref)["program"]
243+
self.assertEqual(after["version"], before["version"])
244+
self.assertEqual(after["counts"]["work_items"], before["counts"]["work_items"])
245+
self.assertEqual(after["counts"]["work_packages"], before["counts"]["work_packages"])
246+
208247
def test_runtime_job_can_belong_to_only_one_work_item(self) -> None:
209248
first = self.repo.record_work_package(self.program_ref, 1, {
210249
"key": "first", "title": "First", "description": "First job",

‎docs/architecture/dirac-digital-twin.html‎

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)