From 3b143879bb857b439db5a5578e26e8bc2aad2145 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 16 Aug 2026 14:43:42 +0000 Subject: [PATCH 1/4] fix(ai): rebase adaptive defaults onto current main Keep ADR-0013 and drop the colliding ADR-0005 copies. Record one Unreleased changelog entry, restore the runtime-adapter and post-evaluation transport regressions, and correct the leftover post-chat docstring so it no longer describes a forced route. Co-authored-by: Seongho Bae --- CHANGELOG.md | 10 ++++ lineageweave/post_chat.py | 2 +- tests/test_adaptive_orchestrator_default.py | 49 +++++++++++++++++++ ..._contextual_orchestrator_default_policy.py | 40 +++++++++++++++ 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 tests/test_adaptive_orchestrator_default.py create mode 100644 tests/test_contextual_orchestrator_default_policy.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bfcaa28f..c717d7489 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to this project are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Changed + +- Product LLM adapters now request contextual-orchestrator + `mode="auto"` rather than forcing a one-model route. The + orchestrator owns the quality-sufficient route, verification, or + conducted workflow; the explicit adjudication `verify` contract + remains unchanged. + ## [0.75.0] - 2026-08-17 ### Added diff --git a/lineageweave/post_chat.py b/lineageweave/post_chat.py index 7ec67e943..d23624bc1 100644 --- a/lineageweave/post_chat.py +++ b/lineageweave/post_chat.py @@ -187,7 +187,7 @@ class ContextualOrchestratorPostChatClient: ``mode="verify"`` exists for (one worker call plus one checked verifier judgment), same reasoning ``adjudication_client`` already uses, not ``keyman_extraction``/``entity_relationship_classification``'s - single-pass ``mode="route"`` structured extraction. + single-pass ``mode="auto"`` structured extraction. """ available = True diff --git a/tests/test_adaptive_orchestrator_default.py b/tests/test_adaptive_orchestrator_default.py new file mode 100644 index 000000000..3de1dd412 --- /dev/null +++ b/tests/test_adaptive_orchestrator_default.py @@ -0,0 +1,49 @@ +"""LineageWeave delegates product-default LLM execution to auto policy.""" + +from __future__ import annotations + +from pathlib import Path + +from lineageweave import post_evaluation + + +def test_post_evaluation_adapter_defaults_to_auto(monkeypatch) -> None: + observed: dict[str, object] = {} + + def fake_post_json(url, payload, *, headers, timeout): + observed.update( + url=url, + payload=payload, + headers=headers, + timeout=timeout, + ) + return {"choices": [{"message": {"content": "{}"}}]} + + monkeypatch.setattr(post_evaluation, "post_json", fake_post_json) + adapter = post_evaluation._OrchestratorCompleteAdapter( + "https://orchestrator.example.test", "inference_token" + ) + adapter.complete([{"role": "user", "content": "Evaluate this evidence."}]) + + assert observed["payload"]["mode"] == "auto" + + +def test_post_evaluation_judge_uses_auto_by_default() -> None: + client = post_evaluation.ContextualOrchestratorPostEvaluationClient( + "https://orchestrator.example.test", "inference_token" + ) + assert client._judge.mode == "auto" + + +def test_runtime_clients_do_not_force_single_model_route() -> None: + package_root = Path(__file__).resolve().parents[1] / "lineageweave" + violations: list[str] = [] + for path in sorted(package_root.glob("*.py")): + text = path.read_text(encoding="utf-8") + if '"mode": "route"' in text or "'mode': 'route'" in text: + violations.append(f"{path.name}: request payload") + if 'mode="route"' in text or "mode='route'" in text: + violations.append(f"{path.name}: constructor/call default") + if 'mode: str = "route"' in text or "mode: str = 'route'" in text: + violations.append(f"{path.name}: typed default") + assert violations == [] diff --git a/tests/test_contextual_orchestrator_default_policy.py b/tests/test_contextual_orchestrator_default_policy.py new file mode 100644 index 000000000..6c5eb5d7c --- /dev/null +++ b/tests/test_contextual_orchestrator_default_policy.py @@ -0,0 +1,40 @@ +"""Contract tests for adaptive contextual-orchestrator consumer defaults.""" +from __future__ import annotations + +from pathlib import Path +import unittest + +ROOT = Path(__file__).resolve().parents[1] +ACTIVE_CLIENTS = ( + "lineageweave/post_summary.py", + "lineageweave/post_evaluation.py", + "lineageweave/keyman_extraction.py", + "lineageweave/commitment_extraction.py", + "lineageweave/post_chat.py", + "lineageweave/entity_relationship_classification.py", +) + + +class AdaptiveOrchestratorDefaultTest(unittest.TestCase): + """Protect production clients from regressing to forced one-model routing.""" + + def test_active_clients_use_auto_and_never_force_route(self) -> None: + for relative in ACTIVE_CLIENTS: + source = (ROOT / relative).read_text(encoding="utf-8") + with self.subTest(path=relative): + self.assertNotIn('"mode": "route"', source) + self.assertNotIn('mode="route"', source) + self.assertNotIn('mode: str = "route"', source) + self.assertTrue( + '"mode": "auto"' in source + or 'mode="auto"' in source + or 'mode: str = "auto"' in source + ) + + def test_high_stakes_adjudication_retains_explicit_checked_override(self) -> None: + source = (ROOT / "lineageweave/adjudication_client.py").read_text(encoding="utf-8") + self.assertIn('"mode": "verify"', source) + + +if __name__ == "__main__": + unittest.main() From 75d6fd84c00a37f5b44708a48dec21ad2219fd8b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 16 Aug 2026 15:17:54 +0000 Subject: [PATCH 2/4] test(ai): lock auto and verify orchestrator transport contracts Split auto and verify client lists so a post-chat docstring cannot satisfy the auto policy. Add wire-level verify assertions for citation chat and lineage adjudication, and name both exceptions in the Unreleased changelog. Co-authored-by: Seongho Bae --- ARCHITECTURE.md | 14 ++-- CHANGELOG.md | 4 +- docs/lineage-bi-research-notes.md | 7 +- tests/test_adaptive_orchestrator_default.py | 64 +++++++++++++++++- ..._contextual_orchestrator_default_policy.py | 65 +++++++++++++++---- 5 files changed, 127 insertions(+), 27 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index f8a83ceb1..605af1bbc 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -84,17 +84,17 @@ flowchart LR | `server.py` | Stdlib HTTP server: `GET /api/lineage` (JSON graph) + static viewer | | `web/index.html` | Self-contained SVG DAG viewer, no build step, no external script dependency | -> **Known local-test-environment limitation:** `adjudication_client.py`'s -> `mode="verify"` call depends on contextual-orchestrator's -> `TaskOrchestrator.route_and_verify`, which as of this writing is still -> an open, unmerged upstream PR +> **Known local-test-environment limitation:** `adjudication_client.py` +> and `post_chat.py` send `mode="verify"` (ADR-0013). That call depends +> on contextual-orchestrator's `TaskOrchestrator.route_and_verify`, +> which as of this writing is still an open, unmerged upstream PR > (`ContextualWisdomLab/contextual-orchestrator#149`). Until it merges, -> the four adjudication/chat tests that exercise `mode="verify"` against +> the live adjudication/chat tests that exercise `mode="verify"` against > a real orchestrator fail with `invalid_mode` (the deployed `main` only > accepts `auto`/`route`/`conduct`) -- confirmed by reproducing the same > `400` directly against the orchestrator's own `/v1/chat/completions`, -> not caused by anything in this repo. `mode="route"` (every other -> pluggable client) is unaffected. +> not caused by anything in this repo. Ordinary product adapters request +> `mode="auto"` and are unaffected. ## Design decisions worth naming diff --git a/CHANGELOG.md b/CHANGELOG.md index c717d7489..5845a6e41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,8 @@ All notable changes to this project are documented here. Format follows - Product LLM adapters now request contextual-orchestrator `mode="auto"` rather than forcing a one-model route. The orchestrator owns the quality-sufficient route, verification, or - conducted workflow; the explicit adjudication `verify` contract - remains unchanged. + conducted workflow. Citation-bearing post-chat and lineage + adjudication keep their explicit `verify` contracts. ## [0.75.0] - 2026-08-17 diff --git a/docs/lineage-bi-research-notes.md b/docs/lineage-bi-research-notes.md index acd55620b..07079230b 100644 --- a/docs/lineage-bi-research-notes.md +++ b/docs/lineage-bi-research-notes.md @@ -254,9 +254,10 @@ classified into the closed `{our_side, counterparty}` set is dropped rather than guessed. N:N organization attachments are slot-filling on that mention (a person may have zero, one, or several affiliations in the same post), not a second independent NER pass. The live client -calls contextual-orchestrator (`mode="route"`) rather than a raw LLM -API so reasoning-effort allocation stays centralized with the -adjudication channel. Proven for real during development against +calls contextual-orchestrator (`mode="auto"`) rather than a raw LLM +API so the orchestration plane can allocate route, verify, or a +deeper workflow; adjudication and post-chat keep explicit +`mode="verify"`. Proven for real during development against `fixtures.ambiguous_keyman_post` when orchestrator credentials are set; the default suite asserts the parser and the never-fake null client. diff --git a/tests/test_adaptive_orchestrator_default.py b/tests/test_adaptive_orchestrator_default.py index 3de1dd412..6b88197d3 100644 --- a/tests/test_adaptive_orchestrator_default.py +++ b/tests/test_adaptive_orchestrator_default.py @@ -4,7 +4,8 @@ from pathlib import Path -from lineageweave import post_evaluation +from lineageweave import adjudication_client, post_chat, post_evaluation +from lineageweave.post_chat import ChatSourceDocument, ContextualOrchestratorPostChatClient def test_post_evaluation_adapter_defaults_to_auto(monkeypatch) -> None: @@ -35,6 +36,67 @@ def test_post_evaluation_judge_uses_auto_by_default() -> None: assert client._judge.mode == "auto" +def test_post_chat_requests_verify_mode(monkeypatch) -> None: + """Citation chat must send verify on the wire, not a docstring mention of auto.""" + + observed: dict[str, object] = {} + + def fake_post_json(url, payload, *, headers, timeout): + observed["payload"] = payload + return { + "choices": [ + { + "message": { + "content": ( + '{"answer_text": "The follow-up names the same bid.",' + ' "cited_source_numbers": [1]}' + ) + } + } + ] + } + + monkeypatch.setattr(post_chat, "post_json", fake_post_json) + client = ContextualOrchestratorPostChatClient( + "https://orchestrator.example.test", "inference_token" + ) + answer = client.answer( + "What happened between these events?", + [ + ChatSourceDocument( + post_id="post-bid-follow-up", + post_title="Bid follow-up", + post_body="Northridge asked to confirm the bid date.", + ) + ], + ) + + assert answer.cited_post_ids == ("post-bid-follow-up",) + assert observed["payload"]["mode"] == "verify" + + +def test_adjudication_requests_verify_mode(monkeypatch) -> None: + """Lineage adjudication must send verify on the wire, not a source substring.""" + + observed: dict[str, object] = {} + + def fake_post_json(url, payload, *, headers, timeout): + observed["payload"] = payload + return {"choices": [{"message": {"content": "0.91"}}]} + + monkeypatch.setattr(adjudication_client, "post_json", fake_post_json) + client = adjudication_client.ContextualOrchestratorAdjudicationClient( + "https://orchestrator.example.test", "inference_token" + ) + confidence = client.judge( + "Quarterly budget review meeting notes", + "Budget review follow-up: revised quarterly numbers", + ) + + assert confidence == 0.91 + assert observed["payload"]["mode"] == "verify" + + def test_runtime_clients_do_not_force_single_model_route() -> None: package_root = Path(__file__).resolve().parents[1] / "lineageweave" violations: list[str] = [] diff --git a/tests/test_contextual_orchestrator_default_policy.py b/tests/test_contextual_orchestrator_default_policy.py index 6c5eb5d7c..24a023e1a 100644 --- a/tests/test_contextual_orchestrator_default_policy.py +++ b/tests/test_contextual_orchestrator_default_policy.py @@ -1,39 +1,76 @@ """Contract tests for adaptive contextual-orchestrator consumer defaults.""" + from __future__ import annotations from pathlib import Path import unittest ROOT = Path(__file__).resolve().parents[1] -ACTIVE_CLIENTS = ( +AUTO_CLIENTS = ( "lineageweave/post_summary.py", "lineageweave/post_evaluation.py", "lineageweave/keyman_extraction.py", "lineageweave/commitment_extraction.py", - "lineageweave/post_chat.py", "lineageweave/entity_relationship_classification.py", ) +VERIFY_CLIENTS = ( + "lineageweave/post_chat.py", + "lineageweave/adjudication_client.py", +) +_ROUTE_MARKERS = ( + '"mode": "route"', + 'mode="route"', + 'mode: str = "route"', +) +_AUTO_MARKERS = ( + '"mode": "auto"', + 'mode="auto"', + 'mode: str = "auto"', +) +_VERIFY_MARKERS = ( + '"mode": "verify"', + 'mode="verify"', + 'mode: str = "verify"', +) + + +def _source(relative: str) -> str: + return (ROOT / relative).read_text(encoding="utf-8") + + +def _contains_any(source: str, markers: tuple[str, ...]) -> bool: + return any(marker in source for marker in markers) class AdaptiveOrchestratorDefaultTest(unittest.TestCase): """Protect production clients from regressing to forced one-model routing.""" - def test_active_clients_use_auto_and_never_force_route(self) -> None: - for relative in ACTIVE_CLIENTS: - source = (ROOT / relative).read_text(encoding="utf-8") + def test_auto_clients_request_auto_and_never_force_route(self) -> None: + for relative in AUTO_CLIENTS: + source = _source(relative) with self.subTest(path=relative): - self.assertNotIn('"mode": "route"', source) - self.assertNotIn('mode="route"', source) - self.assertNotIn('mode: str = "route"', source) + for marker in _ROUTE_MARKERS: + self.assertNotIn(marker, source) self.assertTrue( - '"mode": "auto"' in source - or 'mode="auto"' in source - or 'mode: str = "auto"' in source + _contains_any(source, _AUTO_MARKERS), + f"{relative} must request mode=auto in executable source", ) - def test_high_stakes_adjudication_retains_explicit_checked_override(self) -> None: - source = (ROOT / "lineageweave/adjudication_client.py").read_text(encoding="utf-8") - self.assertIn('"mode": "verify"', source) + def test_verify_clients_keep_checked_judgment_and_never_force_route(self) -> None: + for relative in VERIFY_CLIENTS: + source = _source(relative) + with self.subTest(path=relative): + for marker in _ROUTE_MARKERS: + self.assertNotIn(marker, source) + self.assertTrue( + _contains_any(source, _VERIFY_MARKERS), + f"{relative} must request mode=verify in executable source", + ) + self.assertNotIn( + '"mode": "auto"', + source, + f"{relative} must send verify, not a payload-level auto default", + ) if __name__ == "__main__": From 7b6eeb8be250e1680bc966900a5116e771b8f4c1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 17 Aug 2026 16:43:04 +0000 Subject: [PATCH 3/4] test(ai): require payload literals for auto and verify scans Source-scan regressions now require a payload-level "mode": "auto" or "mode": "verify" literal. A class docstring contrast can no longer satisfy ADR-0013. Co-authored-by: Seongho Bae --- CHANGELOG.md | 4 +- ..._contextual_orchestrator_default_policy.py | 50 +++++++++++-------- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5845a6e41..1aac21442 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,9 @@ All notable changes to this project are documented here. Format follows `mode="auto"` rather than forcing a one-model route. The orchestrator owns the quality-sufficient route, verification, or conducted workflow. Citation-bearing post-chat and lineage - adjudication keep their explicit `verify` contracts. + adjudication keep their explicit `verify` contracts. Source-scan + regressions require those payload literals so a docstring mention + cannot satisfy ADR-0013. ## [0.75.0] - 2026-08-17 diff --git a/tests/test_contextual_orchestrator_default_policy.py b/tests/test_contextual_orchestrator_default_policy.py index 24a023e1a..0900cbe34 100644 --- a/tests/test_contextual_orchestrator_default_policy.py +++ b/tests/test_contextual_orchestrator_default_policy.py @@ -22,26 +22,16 @@ 'mode="route"', 'mode: str = "route"', ) -_AUTO_MARKERS = ( - '"mode": "auto"', - 'mode="auto"', - 'mode: str = "auto"', -) -_VERIFY_MARKERS = ( - '"mode": "verify"', - 'mode="verify"', - 'mode: str = "verify"', -) +_PAYLOAD_AUTO = '"mode": "auto"' +_PAYLOAD_VERIFY = '"mode": "verify"' +_TYPED_AUTO_DEFAULT = 'mode: str = "auto"' +_FORWARDED_MODE = '"mode": mode' def _source(relative: str) -> str: return (ROOT / relative).read_text(encoding="utf-8") -def _contains_any(source: str, markers: tuple[str, ...]) -> bool: - return any(marker in source for marker in markers) - - class AdaptiveOrchestratorDefaultTest(unittest.TestCase): """Protect production clients from regressing to forced one-model routing.""" @@ -51,9 +41,14 @@ def test_auto_clients_request_auto_and_never_force_route(self) -> None: with self.subTest(path=relative): for marker in _ROUTE_MARKERS: self.assertNotIn(marker, source) - self.assertTrue( - _contains_any(source, _AUTO_MARKERS), - f"{relative} must request mode=auto in executable source", + if relative.endswith("post_evaluation.py"): + self.assertIn(_FORWARDED_MODE, source) + self.assertIn(_TYPED_AUTO_DEFAULT, source) + continue + self.assertIn( + _PAYLOAD_AUTO, + source, + f"{relative} must send a payload-level mode=auto literal", ) def test_verify_clients_keep_checked_judgment_and_never_force_route(self) -> None: @@ -62,16 +57,29 @@ def test_verify_clients_keep_checked_judgment_and_never_force_route(self) -> Non with self.subTest(path=relative): for marker in _ROUTE_MARKERS: self.assertNotIn(marker, source) - self.assertTrue( - _contains_any(source, _VERIFY_MARKERS), - f"{relative} must request mode=verify in executable source", + self.assertIn( + _PAYLOAD_VERIFY, + source, + f"{relative} must send a payload-level mode=verify literal", ) self.assertNotIn( - '"mode": "auto"', + _PAYLOAD_AUTO, source, f"{relative} must send verify, not a payload-level auto default", ) + def test_docstring_mode_mention_is_not_a_payload_literal(self) -> None: + """A class docstring contrast must not satisfy the auto/verify scan.""" + + source = ( + '"""Calls the orchestrator with mode="auto", not mode="verify"""\n' + 'body = {"messages": [], "mode": "route"}\n' + ) + self.assertNotIn(_PAYLOAD_AUTO, source) + self.assertNotIn(_PAYLOAD_VERIFY, source) + self.assertIn('mode="auto"', source) + self.assertIn('mode="verify"', source) + if __name__ == "__main__": unittest.main() From dc0c62f1d517898309489e652d2bc099d35f88c9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 17 Aug 2026 23:58:02 +0000 Subject: [PATCH 4/4] docs: record that orchestrator verify is still undeployed contextual-orchestrator#149 closed unmerged. Keep the explicit verify contract for chat and adjudication, and do not fall back to auto while upstream main still rejects that mode. Co-authored-by: Seongho Bae --- ARCHITECTURE.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 605af1bbc..629286b41 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -86,14 +86,19 @@ flowchart LR > **Known local-test-environment limitation:** `adjudication_client.py` > and `post_chat.py` send `mode="verify"` (ADR-0013). That call depends -> on contextual-orchestrator's `TaskOrchestrator.route_and_verify`, -> which as of this writing is still an open, unmerged upstream PR -> (`ContextualWisdomLab/contextual-orchestrator#149`). Until it merges, -> the live adjudication/chat tests that exercise `mode="verify"` against -> a real orchestrator fail with `invalid_mode` (the deployed `main` only -> accepts `auto`/`route`/`conduct`) -- confirmed by reproducing the same -> `400` directly against the orchestrator's own `/v1/chat/completions`, -> not caused by anything in this repo. Ordinary product adapters request +> on contextual-orchestrator's `TaskOrchestrator.route_and_verify`. +> Upstream `ContextualWisdomLab/contextual-orchestrator#149` closed +> unmerged; verify-mode honesty continues on +> `ContextualWisdomLab/contextual-orchestrator#622` and is not on +> deployed orchestrator `main`. LineageWeave talks to that service over +> HTTP (`ORCHESTRATOR_BASE_URL`) and cannot pin or ship an unmerged +> sibling commit. Until a verify-capable orchestrator is deployed, live +> adjudication/chat tests against a real instance fail with +> `invalid_mode` (deployed `main` still accepts only +> `auto`/`route`/`conduct`) -- confirmed by reproducing the same `400` +> against the orchestrator's own `/v1/chat/completions`, not caused by +> anything in this repo. This repo keeps the explicit `verify` contract +> and does not fall back to `auto`. Ordinary product adapters request > `mode="auto"` and are unaffected. ## Design decisions worth naming