-
Notifications
You must be signed in to change notification settings - Fork 1
staged: fix/reconciler-followups-20260606 -> staged-2c3b33832330-1780719283 #690
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -753,7 +753,9 @@ def _normalize_adf_body(body: Any) -> str: | |
| return str(body) if body is not None else "" | ||
|
|
||
|
|
||
| def _apply_inbound_create(mutation, *, client=None, repo_root=None) -> ApplyResult: | ||
| def _apply_inbound_create( | ||
| mutation, *, client=None, repo_root=None, binding_store=None | ||
| ) -> ApplyResult: | ||
| """Materialise a remote Jira issue as a local jira-* ticket. | ||
|
|
||
| Writes a CREATE event (title, ticket_type, priority, description, tags | ||
|
|
@@ -770,6 +772,32 @@ def _apply_inbound_create(mutation, *, client=None, repo_root=None) -> ApplyResu | |
| fields = payload.get("fields") or payload | ||
| jira_key = mutation.target | ||
| local_id = _jira_key_to_local_id(jira_key) | ||
|
|
||
| # Defense-in-depth inbound dedup (ticket 1577). The snapshot differ normally | ||
| # stands down for an already-bound issue by recognising its dso-id:<local_id> | ||
| # label in the fetched fields (bug 4354) — that is the primary production | ||
| # dedup. This guard covers the narrow transient where the snapshot predates | ||
| # the label write-back yet the binding already exists in bindings.json: the | ||
| # differ then mis-emits an inbound CREATE which would materialise a duplicate | ||
| # local ticket. If the target Jira key is already bound, record the mapping | ||
| # and skip materialisation. Cheap (local reverse-index lookup, no Jira GET). | ||
| if binding_store is not None: | ||
| bound_local_id = binding_store.get_local_id(jira_key) | ||
| if bound_local_id: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DSO llm-review — finding 2/6[important] Posted by dso_ci_review.runner; resolve this comment when addressed. |
||
| if repo_root is None: | ||
| repo_root = Path(__file__).parents[4] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DSO llm-review — finding 3/6[important] Posted by dso_ci_review.runner; resolve this comment when addressed. |
||
| mapping_path = repo_root / "bridge_state" / "mapping.json" | ||
| _write_mapping_atomic(mapping_path, bound_local_id, jira_key) | ||
| return ApplyResult( | ||
| mutation.direction, | ||
| mutation.action, | ||
| { | ||
| "local_id": bound_local_id, | ||
| "jira_key": jira_key, | ||
| "dedup_skipped": True, | ||
| }, | ||
| ) | ||
|
|
||
| issuetype = _extract_name(fields.get("issuetype"), "Task") | ||
| ticket_type = _JIRA_TYPE_MAP.get(issuetype, "task") | ||
|
|
||
|
|
@@ -1649,7 +1677,7 @@ def __init__(self, batch_mutation: dict) -> None: | |
| } | ||
|
|
||
|
|
||
| def _apply_typed(mutation, *, client=None, repo_root=None) -> ApplyResult: | ||
| def _apply_typed(mutation, *, client=None, repo_root=None, binding_store=None) -> ApplyResult: | ||
| """Typed-mutation dispatch via _LEAVES. | ||
|
|
||
| Looks up (mutation.direction, mutation.action) in _LEAVES and invokes the | ||
|
|
@@ -1681,17 +1709,24 @@ def _apply_typed(mutation, *, client=None, repo_root=None) -> ApplyResult: | |
|
|
||
| try: | ||
| sig = _inspect.signature(handler) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DSO llm-review — finding 4/4[important] Posted by dso_ci_review.runner; resolve this comment when addressed. |
||
| accepts_repo_root = "repo_root" in sig.parameters or any( | ||
| _has_var_kw = any( | ||
| p.kind is _inspect.Parameter.VAR_KEYWORD for p in sig.parameters.values() | ||
| ) | ||
| accepts_repo_root = "repo_root" in sig.parameters or _has_var_kw | ||
| accepts_binding_store = "binding_store" in sig.parameters or _has_var_kw | ||
| except (TypeError, ValueError): | ||
| # Builtins / C-extensions don't expose signatures: fall back to passing | ||
| # repo_root (legacy behaviour). | ||
| # repo_root (legacy behaviour) but NOT binding_store (only leaves that | ||
| # explicitly declare it consume it — ticket 1577). | ||
| accepts_repo_root = True | ||
| accepts_binding_store = False | ||
|
|
||
| _leaf_kwargs: dict[str, Any] = {"client": client} | ||
| if accepts_repo_root: | ||
| return handler(mutation, client=client, repo_root=repo_root) | ||
| return handler(mutation, client=client) | ||
| _leaf_kwargs["repo_root"] = repo_root | ||
| if accepts_binding_store: | ||
| _leaf_kwargs["binding_store"] = binding_store | ||
| return handler(mutation, **_leaf_kwargs) | ||
|
|
||
|
|
||
| # Exit code signalling that the caller should reschedule this pass. | ||
|
|
@@ -2568,7 +2603,9 @@ def apply( | |
| and hasattr(mutations, "direction") | ||
| and hasattr(mutations, "action") | ||
| ): | ||
| return _apply_typed(mutations, client=client, repo_root=repo_root) | ||
| return _apply_typed( | ||
| mutations, client=client, repo_root=repo_root, binding_store=binding_store | ||
| ) | ||
|
|
||
| # Legacy batch path requires pass_id. | ||
| if pass_id is None: | ||
|
|
@@ -2737,7 +2774,9 @@ def _record_suppression(local_id: str, jira_key: str) -> None: | |
| for mut in inbound_typed: | ||
| if _is_suppressed(getattr(mut, "target", "")): | ||
| continue | ||
| result = _apply_typed(mut, client=client, repo_root=repo_root) | ||
| result = _apply_typed( | ||
| mut, client=client, repo_root=repo_root, binding_store=binding_store | ||
| ) | ||
| result_payload = ( | ||
| getattr(result, "payload", None) if result is not None else None | ||
| ) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -119,7 +119,10 @@ trap restore_snapshot EXIT | |
| LAST_RECONCILER_LOG="" | ||
| run_reconciler() { | ||
| local output | ||
| LAST_RECONCILER_LOG=$(mktemp -t recon-probe.XXXXXX.log) | ||
| # Template form (not `-t`): the -t flag has divergent macOS/GNU semantics | ||
| # and is prohibited by CLAUDE.md rule:mktemp-tmp. Production/orchestrator | ||
| # context, so a literal /tmp template is fine (no per-test TMPDIR contract). | ||
| LAST_RECONCILER_LOG=$(mktemp "/tmp/recon-probe.XXXXXX.log") | ||
|
Comment on lines
+122
to
+125
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Quality: mktemp template has trailing suffix after X's (non-portable)The replacement Was this helpful? React with 👍 / 👎 |
||
| output=$(cd "$RECONCILER_DIR" && python -m dso_reconciler "$@" 2>&1) || true | ||
| printf '%s\n' "$output" > "$LAST_RECONCILER_LOG" | ||
| echo "$output" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ | |
| import importlib.util | ||
| import json | ||
| import sys | ||
| import types | ||
| from pathlib import Path | ||
| from unittest.mock import MagicMock | ||
|
|
||
|
|
@@ -91,6 +92,38 @@ def _make_mutation( | |
| ) | ||
|
|
||
|
|
||
| def _patch_apply_deps(applier, monkeypatch): | ||
| """Stub applier.apply()'s lazy module loaders for an offline, all-inbound run. | ||
|
|
||
| apply() calls ``_load_acli()`` unconditionally to construct the Jira client | ||
| (applier.py ~2716). The real acli-integration.py does | ||
| ``from dso_reconciler.adf import text_to_adf`` at import time, which is | ||
| unresolvable under this file's spec_from_file_location loading scheme | ||
| (modules live under the ``plugins.dso.scripts.dso_reconciler`` package, not a | ||
| bare ``dso_reconciler`` package) — so the real load raises ModuleNotFoundError | ||
| before any inbound dispatch happens. Mirror test_e2e_dedup_pass: stub | ||
| ``_load_acli`` (offline client) and ``_load_concurrency`` (no git in tmp_path). | ||
| """ | ||
| fake_acli = types.ModuleType("acli_integration_stub") | ||
| fake_acli.AcliClient = lambda **_: MagicMock() # type: ignore[attr-defined] | ||
| monkeypatch.setattr(applier, "_load_acli", lambda: fake_acli) | ||
|
|
||
| fake_conc = types.ModuleType("concurrency_stub") | ||
| fake_conc.snapshot_head = lambda _repo_root: "deadbeef" * 5 # type: ignore[attr-defined] | ||
|
|
||
| class _Result: | ||
| ok = True | ||
| event = None | ||
| value = None | ||
|
|
||
| def _rebase_retry(_repo_root, write_fn, **_kwargs): | ||
| write_fn() | ||
| return _Result() | ||
|
|
||
| fake_conc.rebase_retry = _rebase_retry # type: ignore[attr-defined] | ||
| monkeypatch.setattr(applier, "_load_concurrency", lambda: fake_conc) | ||
|
|
||
|
|
||
| def _event_files(tracker_dir: Path, local_id: str) -> list[Path]: | ||
| return sorted((tracker_dir / local_id).glob("*.json")) | ||
|
|
||
|
|
@@ -377,6 +410,7 @@ def test_apply_honours_suppress_pair_drops_subsequent_inbound( | |
| """ | ||
|
|
||
| monkeypatch.setattr(applier, "_file_conflict_bug_ticket", lambda *a, **k: "bug-1") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DSO llm-review — finding 4/6[important] Posted by dso_ci_review.runner; resolve this comment when addressed. |
||
| _patch_apply_deps(applier, monkeypatch) | ||
|
|
||
| # Seed the ticket dir so updates would otherwise apply. | ||
| create = _make_mutation( | ||
|
|
@@ -435,6 +469,7 @@ def test_apply_honours_suppress_pair_drops_subsequent_inbound_via_computed_form( | |
| third match-arm, the later inbound update sneaks past. | ||
| """ | ||
| monkeypatch.setattr(applier, "_file_conflict_bug_ticket", lambda *a, **k: "bug-1") | ||
| _patch_apply_deps(applier, monkeypatch) | ||
|
|
||
| # Seed the ticket dir so an EDIT could otherwise be written. | ||
| create = _make_mutation( | ||
|
|
@@ -477,6 +512,63 @@ def test_apply_honours_suppress_pair_drops_subsequent_inbound_via_computed_form( | |
| assert "SHOULD-BE-DROPPED" not in titles | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DSO llm-review — finding 1/4[critical] Posted by dso_ci_review.runner; resolve this comment when addressed. |
||
|
|
||
|
|
||
| def test_inbound_create_dedups_against_binding_store(applier, mut_mod, fixture_repo): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DSO llm-review — finding 2/4[important] Posted by dso_ci_review.runner; resolve this comment when addressed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DSO llm-review — finding 3/4[important] Posted by dso_ci_review.runner; resolve this comment when addressed. |
||
| """ticket 1577: an inbound CREATE for a Jira key already bound in the binding | ||
| store is deduped — mapping recorded, no phantom local ticket materialised. | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DSO llm-review — finding 5/6[important] Posted by dso_ci_review.runner; resolve this comment when addressed. |
||
| Covers the narrow transient the snapshot differ's 4354 label stand-down | ||
| cannot: the fetched snapshot predates the dso-id:<local_id> label write-back, | ||
| so the differ mis-emits an inbound CREATE, but bindings.json already records | ||
| the binding. The applier-level guard catches it. | ||
| """ | ||
|
|
||
| class _FakeBindingStore: | ||
| def get_local_id(self, jira_key): | ||
| return "uuid-bound-7" if jira_key == "DIG-77" else None | ||
|
|
||
| mutation = _make_mutation( | ||
| mut_mod, | ||
| direction=mut_mod.MutationDirection.inbound, | ||
| action=mut_mod.MutationAction.create, | ||
| target="DIG-77", | ||
| payload={"fields": {"summary": "should NOT materialise", "issuetype": "Task"}}, | ||
| ) | ||
| result = applier._apply_typed( | ||
| mutation, repo_root=fixture_repo, binding_store=_FakeBindingStore() | ||
| ) | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DSO llm-review — finding 6/6[important] Posted by dso_ci_review.runner; resolve this comment when addressed. |
||
| # 1. Result signals a dedup skip bound to the pre-existing local id. | ||
| assert result.payload.get("dedup_skipped") is True | ||
| assert result.payload.get("local_id") == "uuid-bound-7" | ||
|
|
||
| # 2. mapping.json records uuid-bound-7 -> DIG-77. | ||
| mapping_file = fixture_repo / "bridge_state" / "mapping.json" | ||
| assert mapping_file.exists(), "dedup guard must write mapping.json" | ||
| mapping = json.loads(mapping_file.read_text()) | ||
| assert mapping.get("uuid-bound-7") == "DIG-77" | ||
|
|
||
| # 3. No phantom local ticket materialised under the jira-key-derived id. | ||
| assert not (fixture_repo / ".tickets-tracker" / "jira-dig-77").exists(), ( | ||
| "must not materialise a phantom local ticket when already bound" | ||
| ) | ||
|
|
||
| # Regression guard: an UNBOUND inbound create still materialises normally — | ||
| # the stand-down is scoped to bound keys. | ||
| unbound = _make_mutation( | ||
| mut_mod, | ||
| direction=mut_mod.MutationDirection.inbound, | ||
| action=mut_mod.MutationAction.create, | ||
| target="DIG-88", | ||
| payload={"fields": {"summary": "brand new", "issuetype": "Task"}}, | ||
| ) | ||
| applier._apply_typed( | ||
| unbound, repo_root=fixture_repo, binding_store=_FakeBindingStore() | ||
| ) | ||
| assert (fixture_repo / ".tickets-tracker" / "jira-dig-88").exists(), ( | ||
| "an unbound inbound create must still materialise a local ticket" | ||
| ) | ||
|
|
||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # 6. Payload shape tolerance (Defect 1) | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DSO llm-review — finding 1/6
[critical]
plugins/dso/scripts/dso_reconciler/applier.py:785plugins/dso/scripts/dso_reconciler/applier.py:2606(correctness)Posted by dso_ci_review.runner; resolve this comment when addressed.