Skip to content

Commit 7ab343c

Browse files
test: fix fallback chain and stale library snapshot drift in the test suite (#322)
Eight tests failed on main, all from drift rather than a code regression (verified: the same failures reproduced on a clean HEAD worktree before this change). Three independent causes: 1. Model fallback chain missed Fable ------------------------------------ langgraph_engine/model_fallback.py encoded TIER_CHAIN = ("haiku", "sonnet", "opus"), but the authored rule (~/.claude/rules/model-fallback.md) and the library now declare haiku -> sonnet -> opus -> fable -> escalate to user (Fable is the tier above Opus). test_the_encoded_chain_matches_the_rule_document failed. - TIER_CHAIN gains "fable". next_tier/decide/format_fallback_log are chain-driven, so opus now retries at fable and fable is the top tier that escalates to the user; no other logic change was needed. - tests/test_model_fallback.py: the inline copy of the documented chain block now mirrors the rule (four lines, fable top tier) and the parsed length assertion is 4. The mutated-chain negative controls still fail as intended. 2. kg_selection snapshot constants stale vs claude-global-library ----------------------------------------------------------------- tests/test_kg_selection.py pins counts measured against library 29.73.0; the live library is 29.97.4. Re-measured with the same catalogue/adapter: EXPECTED_DOMAIN_COUNT 100 -> 104 EXPECTED_TOTAL_EDGES 7266 -> 7527 EXPECTED_AGENT_COUNT 508 -> 528 EXPECTED_SKILL_COUNT 996 -> 1034 NAIVE_READER_BLIND_SPOT_EDGES 486 -> 490 (domains unchanged at 7) MEASURED_CONTAINER_CENSUS[("bare","type")] 58 -> 62 (others unchanged; total still equals the domain count) Docstrings that quoted the old figures/version were updated to match. tests/test_kg_selection_fallback.py: MEASURED_REAL_MAX_CONFIDENCE 0.864 -> 0.865 (real-task maximum is now 0.8643; minimum 0.685 unchanged, still well clear of the floor). 3. Advertised file counts one behind the tree --------------------------------------------- test_doc_counts_match_the_tree: docs said 105 test files / 96 unit / 493 .py repo-wide / 126 files under tests/; the tree has 106 / 97 / 494 / 127. Updated CLAUDE.md Quick Info and README.md (tests badge, directory tree, Testing section). Verification ------------ Full suite, run in four shards (pytest.ini already sets -q, so progress-line counts were used): 2757 passed, 48 skipped, 0 failed, 0 errors, including tests/integration and tests/e2e. The four previously failing files alone: 104 passed. Closes #322 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013u46KPovSoGvJBAxyJiFoX
1 parent f0541d5 commit 7ab343c

6 files changed

Lines changed: 26 additions & 24 deletions

File tree

‎CLAUDE.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ Claude Workflow Engine is a 3-level LangGraph-based orchestration pipeline for a
2020
| **Status** | Active Development |
2121
| **Primary Location** | langgraph_engine/ |
2222
| **MCP Servers** | 13 servers -- all in separate repos under [techdeveloper-org](https://github.com/orgs/techdeveloper-org/repositories); 1 also keeps an in-engine copy in `src/mcp/` |
23-
| **Total Python Files** | 248 (langgraph_engine/); 493 repo-wide |
24-
| **Test Files** | 105 (96 unit, 5 integration, 3 e2e, 1 load) |
23+
| **Total Python Files** | 248 (langgraph_engine/); 494 repo-wide |
24+
| **Test Files** | 106 (97 unit, 5 integration, 3 e2e, 1 load) |
2525
| **Call Graph** | 578 classes, 3,985 methods, 4 languages (Python/Java/TS/Kotlin) |
2626

2727
---

‎README.md‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
[![Python](https://img.shields.io/badge/Python-3.10%2B-green)](https://python.org)
77
[![CI](https://github.com/techdeveloper-org/claude-workflow-engine/actions/workflows/ci.yml/badge.svg)](https://github.com/techdeveloper-org/claude-workflow-engine/actions/workflows/ci.yml)
88
[![License](https://img.shields.io/badge/License-MIT-yellow)](LICENSE)
9-
[![Tests](https://img.shields.io/badge/Tests-105%20files%20·%205%20integration-brightgreen)](tests/)
9+
[![Tests](https://img.shields.io/badge/Tests-106%20files%20·%205%20integration-brightgreen)](tests/)
1010
[![Discussions](https://img.shields.io/badge/Discussions-GitHub-blueviolet)](../../discussions)
1111

1212
---
@@ -469,8 +469,8 @@ claude-workflow-engine/ # 369 Python files total
469469
├── policies/ # non-.md policy state only (failure-kb.json) — see note below
470470
│ └── 03-execution-system/failure-prevention/failure-kb.json
471471
│
472-
├── tests/ # 105 test_*.py files (126 total Python files)
473-
│ ├── test_*.py # 96 unit tests
472+
├── tests/ # 106 test_*.py files (127 total Python files)
473+
│ ├── test_*.py # 97 unit tests
474474
│ ├── integration/ # 5 integration tests (GitHub, MCP, runtime verification)
475475
│ ├── e2e/ # 3 end-to-end scenario tests
476476
│ └── load/ # 1 concurrency / load test
@@ -628,11 +628,11 @@ ENABLE_HEALTH_SERVER=1 ENABLE_METRICS=1 LOG_FORMAT=json \
628628

629629
## Testing
630630

631-
105 test_*.py files (126 total Python files in `tests/` including conftest and `__init__` files):
631+
106 test_*.py files (127 total Python files in `tests/` including conftest and `__init__` files):
632632

633633
| Category | Files | Notes |
634634
|---|:---:|---|
635-
| Unit tests | 96 | In `tests/test_*.py` — no external dependencies required |
635+
| Unit tests | 97 | In `tests/test_*.py` — no external dependencies required |
636636
| Integration tests | 5 | `tests/integration/` — require live GitHub token or MCP servers |
637637
| E2E tests | 3 | `tests/e2e/` — require full pipeline environment |
638638
| Load tests | 1 | `tests/load/` — enabled with `RUN_LOAD_TESTS=1` |

‎langgraph_engine/model_fallback.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171

7272
_log = logging.getLogger(__name__)
7373

74-
TIER_CHAIN: Tuple[str, ...] = ("haiku", "sonnet", "opus")
74+
TIER_CHAIN: Tuple[str, ...] = ("haiku", "sonnet", "opus", "fable")
7575

7676
RATE_LIMIT_STATUS_CODES: Tuple[int, ...] = (429, 529)
7777

‎tests/test_kg_selection.py‎

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
1. No agent or skill name appears as a string literal on the selection code
88
path outside test fixtures. Asserted by parsing every module in
99
``langgraph_engine/selection`` and intersecting its string literals and
10-
comments against the live 508-agent / 996-skill catalogue. The companion
10+
comments against the live 528-agent / 1034-skill catalogue. The companion
1111
negative plants a real name into a temporary module and requires the same
1212
detector to flag it.
1313
2. Ten sample task descriptions each return a ranked agent set in which every
@@ -25,7 +25,8 @@
2525
its negative substitutes a deliberately non-discriminating scorer and requires
2626
the same measurement to fail.
2727
28-
Every count asserted here was measured against claude-global-library 29.73.0.
28+
Every count asserted here was measured against claude-global-library 29.97.4
29+
(re-measured 2026-09-13; first measured against 29.73.0).
2930
Where a figure could drift with an upstream library release the assertion says
3031
so and is written as a bound rather than an equality.
3132
@@ -70,15 +71,15 @@
7071
SAMPLE_STRIDE = 4
7172
SAMPLE_SIZE = 10
7273

73-
EXPECTED_DOMAIN_COUNT = 100
74-
EXPECTED_TOTAL_EDGES = 7266
75-
EXPECTED_AGENT_COUNT = 508
76-
EXPECTED_SKILL_COUNT = 996
77-
NAIVE_READER_BLIND_SPOT_EDGES = 486
74+
EXPECTED_DOMAIN_COUNT = 104
75+
EXPECTED_TOTAL_EDGES = 7527
76+
EXPECTED_AGENT_COUNT = 528
77+
EXPECTED_SKILL_COUNT = 1034
78+
NAIVE_READER_BLIND_SPOT_EDGES = 490
7879
NAIVE_READER_BLIND_SPOT_DOMAINS = 7
7980

8081
MEASURED_CONTAINER_CENSUS = {
81-
("bare", "type"): 58,
82+
("bare", "type"): 62,
8283
("edges", "type"): 23,
8384
("bare", "edge_type"): 7,
8485
("relationships", "type"): 7,
@@ -308,7 +309,7 @@ class TestKgAdapterConformance:
308309
"""The adapter reads all six measured schema shapes and never empties out."""
309310

310311
def test_every_domain_parses(self, adapter):
311-
"""All 100 domain graphs parse; none degrades to an empty result."""
312+
"""All 104 domain graphs parse; none degrades to an empty result."""
312313
domains = _all_domains()
313314
assert len(domains) == EXPECTED_DOMAIN_COUNT
314315

@@ -322,7 +323,7 @@ def test_every_domain_parses(self, adapter):
322323
def test_container_and_edge_type_census_matches_the_measured_shapes(self, adapter):
323324
"""The six container/edge-type combinations occur at their measured counts.
324325
325-
Counts are for library 29.73.0. An upstream release may legitimately
326+
Counts are for library 29.97.4. An upstream release may legitimately
326327
move a domain between shapes; what must not change is that all six
327328
forms are handled and every domain lands in one of them.
328329
"""
@@ -355,7 +356,7 @@ def test_edge_totals_agree_with_the_master_catalogue_declaration(self, adapter):
355356
assert total == EXPECTED_TOTAL_EDGES
356357

357358
def test_the_naive_edges_only_reader_loses_edges_this_adapter_keeps(self, adapter):
358-
"""NEGATIVE CONTROL: the pre-ADR-015 reader silently drops 486 edges.
359+
"""NEGATIVE CONTROL: the pre-ADR-015 reader silently drops 490 edges.
359360
360361
If this control ever passes -- if the naive reader loses nothing -- then
361362
the assertion above is no longer testing anything, because the corpus

‎tests/test_kg_selection_fallback.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
EXPECTED_SPRINT_TITLES = 37
7272

7373
MEASURED_REAL_MIN_CONFIDENCE = 0.685
74-
MEASURED_REAL_MAX_CONFIDENCE = 0.864
74+
MEASURED_REAL_MAX_CONFIDENCE = 0.865
7575

7676
FLOOR_ABOVE_EVERY_REAL_TASK = 0.95
7777
FLOOR_BELOW_EVERY_CANDIDATE = 0.0

‎tests/test_model_fallback.py‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,10 @@ def _chain_line(*tokens):
8787
[
8888
"## Fallback Chain",
8989
"```",
90-
_chain_line("haiku", "sonnet", "opus", "escalate to user"),
91-
_chain_line("sonnet", "opus", "escalate to user"),
92-
_chain_line("opus", "escalate to user (no further fallback)"),
90+
_chain_line("haiku", "sonnet", "opus", "fable", "escalate to user"),
91+
_chain_line("sonnet", "opus", "fable", "escalate to user"),
92+
_chain_line("opus", "fable", "escalate to user"),
93+
_chain_line("fable", "escalate to user (no further fallback, Fable is the top tier)"),
9394
"```",
9495
"",
9596
]
@@ -220,7 +221,7 @@ def test_the_parser_reads_an_inline_copy_of_the_documented_block(self):
220221
"""
221222
parsed = parse_documented_chain(DOCUMENTED_CHAIN_BLOCK)
222223
assert parsed == TIER_CHAIN
223-
assert len(parsed) == 3
224+
assert len(parsed) == 4
224225

225226
def test_the_parser_rejects_a_mutated_chain(self):
226227
"""NEGATIVE CONTROL: the same parser must disagree when the chain is wrong.

0 commit comments

Comments
 (0)