test(langgraph): capture content_not_contains edge condition regression (#95) #99

Merged
CoreRasurae merged 1 commits from tdd/m1-pure-graph-content-not-contains into master 2026-08-04 10:07:51 +00:00

1 Commits

Author SHA1 Message Date
CoreRasurae 46bff0d451 test(langgraph): capture content_not_contains edge condition regression (#95)
CI / lint (pull_request) Successful in 54s
CI / typecheck (pull_request) Successful in 1m48s
CI / security (pull_request) Successful in 1m20s
CI / quality (pull_request) Successful in 1m17s
CI / build (pull_request) Successful in 1m53s
CI / integration_tests (pull_request) Successful in 4m31s
CI / unit_tests (pull_request) Successful in 5m45s
CI / coverage (pull_request) Successful in 5m3s
CI / status-check (pull_request) Successful in 6s
CI / security (push) Successful in 1m22s
CI / quality (push) Successful in 1m55s
CI / build (push) Successful in 2m9s
CI / benchmark (pull_request) Failing after 21m27s
CI / integration_tests (push) Successful in 4m15s
CI / unit_tests (push) Successful in 5m59s
CI / typecheck (push) Failing after 15m5s
CI / lint (push) Failing after 15m13s
CI / coverage (push) Has been skipped
CI / status-check (push) Failing after 13s
CI / benchmark (push) Successful in 22m57s
Adds a failing-first Behave regression test proving issue #95:
PureLangGraph._evaluate_edge_condition() has no branch for
content_not_contains (Actor Configuration Standard §5.4), so it falls
into the "unknown condition type" catch-all and always returns True.
When a classifier node has sibling content_contains/content_not_contains
edges (the classic if/else idiom), both fire whenever the configured
text is present, so _get_next_nodes() returns two targets instead of
one.

The scenario is tagged @tdd_issue, @tdd_issue_95, and
@tdd_expected_fail per the project's TDD issue-capture workflow. No
runtime hook previously existed to make @tdd_expected_fail meaningful,
so this commit also adds TddExpectedFailPolicy (features/tdd_expected_fail.py)
and wires it into features/environment.py's after_step/after_scenario
hooks: it flips an @tdd_expected_fail scenario's AssertionError failures
to passing (proving the bug is still present), and fails the scenario
if it ever passes outright without an inversion (catching a stale tag
left after the bug was actually fixed). Non-AssertionError failures are
never inverted.

The actual fix lands separately on bugfix/m1-pure-graph-content-not-contains
per issue #95.

Addresses PR #99 reviews (rui.hu):

- Major: the scenario's premise conflicted with §5.4's Honored-By table,
  which listed content_not_contains as honored only by "Stream router,
  conditional nodes, bridge router" - excluding pure-graph edges. A full
  read of the spec confirmed this was a real, consistent restriction
  (every content_not_contains example was a stream-router split; the
  spec's only graph-edge branching example used two content_contains
  conditions, not a contains/not-contains pair), not an obvious
  omission. Per maintainer decision, the original intent was always
  symmetric with content_contains ("All subsystems"), so this commit
  adds docs/adr/ADR-2034-content-not-contains-all-subsystems.md and
  revises docs/index.md to v1.1.0: §5.4 Honored By extended to "All
  subsystems" for content_not_contains, plus a new §21.1 Revision
  History section.
- Minor: TddExpectedFailPolicy now enforces the CONTRIBUTING.md
  tag-combination rules (@tdd_issue_<N> requires @tdd_issue;
  @tdd_expected_fail requires both @tdd_issue and a @tdd_issue_<N>),
  raising AssertionError on a malformed combination instead of silently
  under-validating the scenario.
- Nits: removed the unused context.results assignment in the
  background step; reordered environment.py so the recursion-limit
  comment/call no longer sits beside the new first-party import as if
  commenting on it.
- Minor: TddExpectedFailPolicy's guard-rail branches - stale-tag
  detection, malformed tag-combination validation, and non-AssertionError
  failures never being inverted - were only exercised indirectly through
  the single happy-path @tdd_issue_95 scenario. Adds
  features/unit_tdd_expected_fail_policy.feature (7 scenarios) and its
  step definitions, which construct behave.model.Scenario/Step objects
  directly and invoke TddExpectedFailPolicy's classmethods against a
  throwaway context, so each guard-rail branch is exercised in isolation
  without depending on a real scenario's outcome (a scenario tagged to
  deliberately go stale or malformed would otherwise fail the real
  suite). Kept in this same commit rather than deferred to a fast-follow
  PR, since none of this has merged to master yet.

All fixes are folded into this same commit rather than split across
PRs, since none of this has merged to master yet - it is one feature
still being iterated on.

Refs: #95, #96
2026-08-04 09:49:02 +00:00