Implements the three-tag TDD bug-capture system in Robot Framework via a
Listener v3 module, paralleling the Behave implementation. Tests tagged
tdd_expected_fail that fail have their result inverted to PASS (bug still
exists); tests that unexpectedly pass are inverted to FAIL with guidance.
Addresses all 15 findings from code review (PR !673, reviewer hamza.khyari):
P2 fixes:
- Added idempotency guard (_processed_tests set) to prevent double-inversion
when the listener is loaded twice in the same process.
- Rewrote normal-test-unaffected check to run alongside a tdd_expected_fail
fixture in a single Robot invocation, proving the listener is loaded and
selectively applies rather than being a tautological pass.
P3 fixes:
- Added output.xml existence guard with clear diagnostics in _run_fixture.
- Documented intentional use of data.tags (static definition) vs result.tags
(runtime-modifiable) in end_test docstring.
- Added SKIP status test fixture and integration test case.
- Added message content assertion in cmd_expected_fail_inverted.
- Tightened substring assertions to match specific error text.
- Added tdd_expected_fail-alone fixture (both companions missing).
- Added close() hook to clear _validation_errors and _processed_tests.
- Simplified _run_fixture return type to tuple[str, str].
- Changed listener path resolution from CWD-relative to __file__-relative
in noxfile.py (integration_tests, slow_integration_tests, e2e_tests).
P4 fixes:
- Added __all__ declaration to helper module.
- Changed module docstring from "mirroring" to "paralleling".
- Added comment documenting accepted XML parsing risk (self-generated XML).
Additional fixes:
- Increased M4 E2E plan-tree test timeout from 30s to 120s (pre-existing
timeout failure unrelated to this feature).
Quality gates (post-rebase onto latest master):
- nox -s lint: PASS
- nox -s typecheck: PASS (0 errors)
- nox -s unit_tests: PASS (10,700 scenarios)
- nox -s integration_tests: PASS (1,505 tests)
- nox -s coverage_report: PASS (97.9% >= 97% threshold)
- nox -s benchmark: PASS
- nox -s docs: PASS
- nox -s build: PASS
- nox -s security_scan: PASS
- nox -s dead_code: PASS
ISSUES CLOSED: #628
TDD expected-fail tests proving bug #822 exists:
CheckpointService.rollback_to_checkpoint() returns a successful
RollbackResult but does not execute git reset --hard. Files modified
after the checkpoint remain unchanged after rollback.
Also fixes Robot Framework timeout robustness across the entire test
suite: all Run Process calls now use on_timeout=kill (prevents
SIGTERM-induced -15 exit codes under CI load) and timeouts increased
to 120s (prevents premature kills during heavy parallel execution).
ISSUES CLOSED: #839
Write Behave scenario and Robot Framework test proving that
SubplanService.spawn() only creates metadata (SubplanStatus records
and SpawnMetadata) without creating real child Plan domain objects
or triggering lifecycle progression. Tests are tagged
@tdd_expected_fail so CI passes via result inversion.
ISSUES CLOSED: #838
Add Robot Framework E2E test suite robot/e2e/m2_acceptance.robot exercising
M2 acceptance criteria with zero mocking. Test creates a temp git repo with
sample project files, registers a custom actor via CLI, sets up resource and
project, creates an action referencing the actor, and runs the full plan
lifecycle (use → execute strategize → execute → diff → apply). Validates
actor YAML compilation, skill registry, tool lifecycle, and LLM integration
through real CLI invocations with real provider API keys. Uses flexible
structural assertions and expected_rc=None for LLM-dependent commands.
ISSUES CLOSED: #742
Fixed 5 bugs preventing the M1 E2E acceptance test from passing:
1. _get_lifecycle_service() in action.py and plan.py bypassed the DI
container, creating PlanLifecycleService without UnitOfWork. All
plan/action data was in-memory only and lost between subprocess
calls. Now uses container.plan_lifecycle_service() for DB persistence.
2. `plan execute` CLI only called service.execute_plan() (a pure state
transition) without running PlanExecutor phase processing. Rewrote
to detect the plan's current phase/state and dispatch synchronously:
Strategize/queued → run_strategize(), Strategize/complete → transition
+ run_execute(), Execute/queued → run_execute().
3. `plan apply` CLI had no plan_id argument. Added optional positional
plan_id with _lifecycle_apply_with_id() that drives the plan through
Apply/queued → Apply/processing → Apply/applied.
4. Preflight guardrail in start_strategize() built action_registry from
the in-memory _actions dict only. Added get_action(plan.action_name)
call to load the action from DB into cache before the guardrail check.
5. Robot Framework Create File syntax used continuation lines producing
9 arguments instead of 1. Fixed to use Catenate SEPARATOR=\n then
pass single variable to Create File. Also fixed --branch main to
--branch master (git init default).
update mocks for execute_plan CLI changes across unit and integration tests
The new execute_plan() command calls _get_plan_executor() and
service.get_plan(plan_id) for phase/state detection. Existing tests
only mocked _get_lifecycle_service, so MagicMock defaults caused
phase/state comparisons to fail.
Changes across 14 files:
- Patch _get_plan_executor in all test setups that invoke the CLI
execute command (Behave step files + Robot helper scripts)
- Set service.get_plan.return_value to real Plan objects with correct
phase/state so the execute_plan dispatch logic works
- Fix error-path tests to use STRATEGIZE/COMPLETE plans so the error
side_effects are actually reached
- Fix "Multiple plans eligible" → "Multiple plans ready" message text
to match existing test expectations
increase Robot Framework subprocess timeouts for CI resource contention
Three integration tests were timing out in CI due to resource contention
when pabot runs multiple test suites in parallel. All three pass locally
and the timeouts were simply too tight for constrained CI environments.
- tdd_session_create_di.robot: 30s → 90s (DI container init + DB setup)
- database_integration.robot: 60s → 120s (Run Python Script keyword)
- m3_e2e_verification.robot: 60s → 120s (correction-live-revert spawns
3 sequential CLI subprocesses with full container initialization)
ISSUES CLOSED: #789
Added Robot Framework E2E test suite for M1 milestone acceptance criteria.
Tests the complete plan lifecycle (action create → resource add → project
create → plan use → plan execute strategize → plan execute → plan diff →
plan apply) with real LLM API keys and no mocking.
Key implementation details:
- Uses openai/gpt-4o-mini as strategy/execution actor (cost-effective)
- Simple definition_of_done: "Create a file called HELLO.md"
- Creates isolated temp git repo via Create Temp Git Repo keyword
- Extracts plan ID via ULID regex from plain-text output
- Uses expected_rc=None for LLM-dependent steps (execute, diff, apply)
to handle non-deterministic LLM behavior gracefully
- Flexible structural assertions: checks rc, output presence, git log
- Skips gracefully when no LLM API keys (ANTHROPIC/OPENAI) are set
- Tagged [E2E] so it runs only in nox -s e2e_tests session
ISSUES CLOSED: #741
- Replace `rc == 0 or rc == 1` with strict `rc == 0` in resource type
list test so failures are no longer silently accepted
- Capture and assert the return code of the Suite Setup database schema
creation to fail fast if the setup itself is broken
apply strict RC checks to resource_cli.robot
- Capture return value of Run Process in Suite Setup and assert rc==0
- Replace tolerant RC check (rc==0 or rc==1) with strict Should Be Equal
As Integers check for resource type list test case
broaden exception handling in resource CLI commands
Add catch-all `except Exception` handler after each `except
CleverAgentsError` block in all 14 resource CLI command handlers.
This ensures unexpected exceptions (e.g. sqlalchemy.exc.OperationalError)
are caught and displayed gracefully instead of producing raw tracebacks.
re-raise typer.Abort/Exit in broad exception handlers
The `except Exception` handlers added in the previous commit
inadvertently caught typer.Abort and typer.Exit, which are
subclasses of Exception (via click.exceptions). This turned
successful CLI exits into aborts and double-handled already-caught
errors, breaking integration tests that rely on normal typer exit
behaviour.
Add an isinstance guard to re-raise typer.Abort and typer.Exit
before the catch-all handler runs.
Fixes#896
- Remove silent retry logic in Run Python Script that masked intermittent
failures by retrying on empty stdout + non-zero RC
- Change output filter from 'in' to 'startswith' so only lines beginning
with log prefixes are filtered, not lines containing them mid-string
- Remove [error from the filter list so error lines are never silently
discarded from test output
Fixes#898
Replace Run Keyword And Return Status soft checks with direct assertions
in three test cases:
- Docstring check now uses Should Contain directly instead of logging
WARN, so missing docstrings cause a hard test failure.
- Settings file (config/settings.py) existence is now a hard File Should
Exist assertion instead of a conditional that silently skips all
checks when the file is absent.
- Exceptions file (core/exceptions.py) existence is now a hard File
Should Exist assertion instead of a conditional that silently skips
the exception hierarchy checks when the file is absent.
Add rc == 0 assertions to ~11 intermediate Run Process calls in core_cli_commands.robot that were used as setup steps without any return code verification. If any setup command (init, tell, build, context add, plan new) fails silently, subsequent test assertions could pass for wrong reasons. All setup commands now capture their result and assert rc == 0.
- Remove Require OpenAI Key skip guard that silently skipped the suite
- Replace Run Keyword And Return Status soft RC check with direct assertion
- Convert conditional LaTeX presence check to hard assertions
- Convert conditional latex_source context field check to hard assertion
- Remove dead stderr checks (stderr redirected to stdout via stderr=STDOUT)
Replace the tolerant "rc == 0 or rc == 1" assertion with a strict
"rc == 0" check for the diagnostics --check command. In a clean test
environment, diagnostics should complete without finding errors, and the
previous assertion could not distinguish a legitimate diagnostic failure
from a command crash.
Remove the Require OpenAI Key keyword and its call from Suite Setup.
Tests now fail with a real error when OPENAI_API_KEY is absent,
providing honest signal instead of silently skipping.
Add four CLI-based integration test cases to the M5 E2E verification suite
that exercise the exact commands from the v3.4.0 milestone description via
real subprocess calls to `python -m cleveragents`.
CLI test cases:
- CLI Project Create Large Project: `agents project create local/large-project`
- CLI Resource Add Git Checkout: `agents resource add git-checkout ...`
- CLI Project Link Resource: `agents project link-resource ...`
- CLI Project Show Displays Linked Resource: `agents project show ...`
Each test creates an isolated temp directory with its own CLEVERAGENTS_HOME,
initialises a workspace via `agents init`, runs the target CLI command as a
subprocess with `on_timeout=kill`, asserts zero exit code and verifies
expected output, then tears down the temp directory.
Bug fix: ProjectResourceLinkRepository.create_link() and remove_link() only
called session.flush() without session.commit(), causing linked resource data
to be silently lost between sessions. Added session.commit() to both methods,
plus finally: session.close() to match the session-factory lifecycle pattern
used by all other mutating repository methods. Added session.refresh() and
session.expunge() before return in create_link() so the returned ORM instance
is fully loaded and usable in detached state after session close.
Regression guard: Added two cross-session persistence Behave scenarios
(project_repository.feature) that open a new session from the same engine
after create_link/remove_link and verify the operation was durably committed.
Test improvements from review feedback (rounds 1 and 2):
- Per-test CLEVERAGENTS_HOME isolation via env: override on all Run Process
calls to prevent shared state between tests.
- Stronger ULID-based assertions: CLI tests capture the resource_id ULID from
resource show output and verify the exact ID in project show output.
- Regex-based branch assertion (branch.*main) instead of generic string match.
- Test 4 differentiated from Test 3 by verifying resource show independently
after linking and asserting the specific resource ULID.
- Documentation noting context tier and ACMS criteria are validated at the
Python API level via helper_m5_e2e_verification.py.
- Updated ProjectResourceLinkRepository class docstring documenting commit
and close behaviour of mutating methods.
- CHANGELOG entry for both test additions and production bug fix.
- Redundant Library imports removed, --format plain on project list, comments
explaining fake repo directories, standardised Run Process line style.
Context/ACMS CLI coverage is intentionally not added because the CLI does not
yet expose dedicated context/ACMS inspection commands. These criteria are
validated at the Python API level via helper_m5_e2e_verification.py.
ISSUES CLOSED: #496
Implements AuditEventSubscriber that subscribes to all 9 security-relevant
EventType members and persists redacted audit entries via AuditService.record().
Key components:
- AuditEventSubscriber: bridges EventBus and AuditService (SEC7)
- SECURITY_EVENT_MAP: maps EventType enum to audit type strings
- Redaction via redact_dict() on event details before persistence
- Graceful error handling: failures logged, never propagated
Post-review fixes applied:
- BUG-1: Remove dead correlation_id null-check guard (DomainEvent.correlation_id
is always non-None via ULID default_factory)
- SEC-2: Redact exception messages in warning logs via redact_value() to prevent
potential leakage of sensitive internal state (e.g. DB connection strings)
- PERF-3: Pre-generate unique DomainEvent instances in ASV benchmark setup to
avoid skew from reusing a single frozen object
- Wire event_bus from the DI container into CorrectionService (plan.py),
ConfigService (config.py, skill.py x2, server.py), and
PersistentSessionService (session.py) at their CLI construction sites.
Closes#581
Replace container.resolve.return_value with container.decision_service.return_value
to match actual CLI code (plan.py uses container.decision_service()). This was the
residual pattern from before the DI migration in this same PR.
Fixes CI: Robot.Plan Correct Tree Wiring (2 of 3 tests failing with exit code 1)
Added dedicated E2E test infrastructure completely separate from the existing
integration test suite. E2E tests use zero mocking — they exercise the real
CleverAgents CLI with real LLM API keys.
Key changes:
- New e2e_tests nox session running Robot Framework with --include E2E tag
filter against robot/e2e/ directory. Uses sequential robot (not pabot)
since E2E tests hit real API endpoints with rate limits. Propagates
ANTHROPIC_API_KEY, OPENAI_API_KEY, and GOOGLE_API_KEY from environment.
Output goes to build/reports/robot-e2e/ to avoid artifact collisions.
- Existing integration_tests session now excludes E2E-tagged tests via
--exclude E2E on the pabot invocation.
- New robot/e2e/common_e2e.resource provides shared E2E keywords: suite
setup/teardown with per-suite isolation (no mock AI), graceful skip when
LLM API keys are absent, CLI runner keyword, flexible output assertions,
and temporary git repo fixture creation.
- Minimal smoke test (robot/e2e/smoke_test.robot) validates the harness by
running agents --version and agents --help. Does not require LLM keys.
- Dedicated e2e_tests CI job in .forgejo/workflows/ci.yml injects LLM API
keys from Forgejo secrets. Runs independently (no needs dependencies)
and does not block regular CI.
- e2e_tests is deliberately NOT in the default nox sessions list since it
requires real API keys not present in all environments.
ISSUES CLOSED: #740
Replace CliRunner + unittest.mock.patch with subprocess.run for all
21 CLI-facing test functions across the M1-M6 E2E verification helpers.
Application code fixes:
- action.py: _get_lifecycle_service() uses container.plan_lifecycle_service()
- plan.py: _get_lifecycle_service() uses container.plan_lifecycle_service()
- plan.py: three container.resolve(DecisionService) → container.decision_service()
Test infrastructure:
- New robot/helper_e2e_common.py with shared subprocess utilities
(run_cli, setup_workspace with DB migrations, cleanup_workspace)
- M1-M4, M6 helpers refactored to use run_cli() with real SQLite DB
- M5 unchanged (0 CLI tests, all domain-level)
- TDD detection updated to recognise run_cli() as subprocess invocation
- Remove @tdd_expected_fail from TDD feature + robot tags
- Update 8 Behave step files that mocked container.resolve() to use
container.decision_service() / container.plan_lifecycle_service()
Implemented the three-tag TDD bug-capture system in Behave environment hooks:
- Added tag validation in before_scenario: @tdd_bug_<N> requires @tdd_bug,
@tdd_expected_fail requires both @tdd_bug and @tdd_bug_<N>
- Added result inversion via Scenario.run() wrapper installed in before_all:
scenarios tagged @tdd_expected_fail that fail are reported as passed
(expected failure), and scenarios that unexpectedly pass are reported as
failed with guidance to remove the tag
- Added helper functions validate_tdd_tags() and should_invert_result()
- Added inline documentation referencing CONTRIBUTING.md > TDD Bug Test Tags
- Added Behave test scenarios for tag validation and inversion behavior
- Extract apply_tdd_inversion() as a public, testable function that
encapsulates all inversion logic with proper guards
- Refactored handle_tdd_expected_fail() to delegate to apply_tdd_inversion()
after tag validation, eliminating ~55 lines of duplicated inversion logic
- Tag validation errors in handle_tdd_expected_fail() are now logged at
WARNING level instead of being silently swallowed
- Add hook_failed guard: never invert infrastructure/hook errors
- Add was_dry_run guard: skip inversion when no test actually ran
- Add non-AssertionError guard: warn and skip inversion for exceptions
that likely indicate infrastructure problems, not the captured bug
- Log exception details at DEBUG level before clearing (previously
discarded silently)
- Attach synthetic AssertionError to last step on unexpected pass so
the failure reason appears in standard Behave formatter output
ISSUES CLOSED: #627
Add Behave and Robot Framework TDD tests that use AST analysis to
inspect the M1-M6 E2E verification helper files and prove that all
21 CLI-facing test functions use unittest.mock.patch + CliRunner
instead of exercising the real CLI via subprocess invocation.
The tests verify three properties that currently fail (proving #658):
- At least one CLI-facing test uses subprocess instead of CliRunner
- At least one CLI-facing test exercises the real service layer
- Every suite with CLI tests has at least one unmocked test
Tagged @tdd_expected_fail so the tests pass CI while the bug is
present. The @tdd_expected_fail tag inverts the result: the tests
pass because the underlying assertions fail (confirming the bug).
Once #658 is fixed, the tag is removed and the tests run normally.
Files added:
- features/tdd_e2e_mock_only_coverage.feature (3 scenarios)
- features/steps/tdd_e2e_mock_only_coverage_steps.py
- robot/tdd_e2e_mock_only_coverage.robot (3 test cases)
- robot/helper_tdd_e2e_mock_only_coverage.py
ISSUES CLOSED: #697
Register PersistentSessionService in the DI Container so that
'agents session list' (and all other session subcommands) no longer
throw AttributeError due to a missing 'db' provider.
Changes:
- Add _build_session_service() factory and session_service provider to
Container, with targeted table creation for session/session_messages
only (avoids bypassing Alembic for the full schema).
- Add auto_commit parameter to SessionRepository and
SessionMessageRepository; when True each method commits and closes
its own database session, preventing resource leaks in CLI context.
- Rewrite _get_session_service() to resolve via container.session_service()
with module-level caching.
- Add (DatabaseError, AttributeError) error handling with logging to all
seven session subcommands (list, create, show, delete, export, import,
tell).
- Remove @tdd_expected_fail tags from all session test files so they run
as proper regression tests.
ISSUES CLOSED: #554, #570, #680
Renamed src/cleveragents/acp/ to src/cleveragents/a2a/ and all 13
Acp* classes to A2a* per ADR-047 (A2A Standard Adoption). Updated
all imports, structlog event names (acp.* → a2a.*), field names
(acp_version → a2a_version), and test references across the entire
codebase. This is a cosmetic rename only — no behavioral changes.
ISSUES CLOSED: #688
Implement ContainerToolExecutor for delegating tool invocations to
devcontainer environments with full I/O forwarding. Add PathMapper for
bidirectional host/container path translation. Wire container routing
into ToolRunner with graceful fallback when no executor is configured.
Add container_metadata field to ToolInvocation for tracking execution
context.
New modules:
- tool/container_executor.py: ContainerToolExecutor, ContainerConfig,
ContainerMetadata, ContainerExecutionError, ContainerTimeoutError
- tool/path_mapper.py: PathMapper with host_to_container/container_to_host
Modified:
- tool/runner.py: container execution routing via ExecutionEnvironment
- domain/models/core/change.py: container_metadata on ToolInvocation
- tool/__init__.py: new public exports
Review fixes applied:
- Add Alembic migration m6_004 for container_metadata_json column
- Enforce _MAX_OUTPUT_BYTES (50 MiB) truncation in _run_command()
- Fix path traversal in sync_results_to_host (Path.is_relative_to)
- Allow spaces in _looks_like_path() for valid filesystem paths
- Preserve negative exit codes from signal kills in metadata
- Add default=str to json.dumps(invocation.arguments) safety net
- Log warnings when path mapping recursion depth exceeded
- Warn when devcontainer binary not found on PATH
- Use default allow_nan for host-path JSON validation in runner.py
(only container path requires RFC 7159 strict mode)
- Reject URL-like patterns in _looks_like_path() to avoid false
positives on API routes, protocol-relative URIs, and query strings
- Add extract_container_metadata() static helper on
ContainerToolExecutor as bridge for ToolInvocation wiring
- Use raw_stdout bytes in sync_results_to_host to prevent
binary file corruption from text-mode decode/re-encode
- Apply posixpath.normpath() in workspace_folder validator and
reject path components containing '..'
- Check result.timed_out in sync_results_to_host and raise
ContainerTimeoutError instead of always raising ContainerExecutionError
- Detect overlapping host_root/container_root in PathMapper and
raise ValueError to prevent corrupt bidirectional mappings
- Wrap host-side I/O in sync_results_to_host with try/except
OSError to produce ContainerExecutionError on write failure
- Enforce int(timeout) in _build_exec_command to prevent shell
injection via malicious objects with __str__ methods
- Change ToolResult validator from 'not self.error' to
'self.error is None' so empty-string errors are accepted
- Iterate required list directly in ToolRunner schema validation
to detect fields listed in required but absent from properties
Closes#515
Add Behave and Robot Framework TDD tests that exercise the session list
command when no database file exists. Tagged @tdd_expected_fail so that
the tests pass CI while the bug is present (the Container has no db
provider, causing AttributeError). Once the bugfix removes the tag,
these become permanent regression tests.
ISSUES CLOSED: #683
Wire per-service retry policies and circuit breakers into the service
layer via ServiceRetryWiring, backed by ServiceRetryPolicyRegistry and
configurable through Settings environment variables.
Production hardening from code review:
- Fix TOCTOU race in CircuitBreaker._on_success (half-open state)
- Add half-open probe limit to prevent unbounded concurrent requests
- Track all exception types for circuit breaker failure counting
- Detect async callables wrapped in functools.partial and callable objects
- Enforce spec-compliant 2s minimum for linear backoff strategy
- Enforce 0.1s floor for fixed backoff strategy
- Add retry amplification guard via contextvars nesting depth tracking
- Cap total retry wall-clock time at 300s (MAX_RETRY_TOTAL_TIMEOUT)
- Sanitize exception messages in retry logs to prevent secret leakage
- Fix wrap_service_method TOCTOU by holding cache lock for full operation
- Deep-copy default policies to prevent cross-policy mutation
- Warn on unknown override keys in apply_overrides
- Guard apply_overrides against non-dict and deeply nested JSON values
- Read circuit breaker state under lock in is_circuit_open
- Catch RecursionError in JSON config parsing
- Add total_timeout + nesting guard to retry_service_operation decorator
- Extend secret sanitization to Authorization headers, private_key,
connection_string, and access_key patterns
- Enforce 0.1s floor on jitter backoff strategy
- Cache wait strategies per service in ServiceRetryWiring (M3)
- Reset failure_count to 0 when entering half-open from open (M6)
- Use cached _get_wait_strategy() in execute()/async_execute()
- Move circuit-open logging out of _on_failure lock scope to prevent
holding the lock during potentially slow I/O (F1)
- Pass total_timeout=MAX_RETRY_TOTAL_TIMEOUT to wrap_service_method
retry_service_operation call for consistency with execute() (F4)
- Capture failure_count into local variable inside lock scope before
logging outside the lock, preventing stale reads from concurrent
threads in CircuitBreaker.call() and async_call() (F1)
- Deep-copy module-level DEFAULT_DATABASE_RETRY and DEFAULT_CIRCUIT_BREAKER
in ServiceRetryPolicyRegistry.get() for auto-generated unknown service
policies, preventing shared mutable state corruption (F1)
- Unify CircuitBreaker to a single threading.Lock for sync and async (P1-1)
- Restore BaseException permit in half-open path to prevent permit leak (P1-6)
- Prevent CircuitBreakerOpen cascading into failure_count (S2)
- Protect all logger calls with contextlib.suppress (S3, S4)
- Replace time.time() with time.monotonic() for monotonic timing (S5)
- Add distinct log events for half-open and closed transitions (S11, S12)
- Track pre-existing services so second apply_settings_defaults only
targets newly registered services (P1-2)
- Lazy circuit breaker creation via _get_or_create_cb() (P1-3)
- Reject async callables in sync execute() with TypeError (P1-5)
- Strengthen retry predicate to retry_if_exception_type(Exception) &
retry_if_not_exception_type(CircuitBreakerOpen) (S1)
- Add lock on _get_wait_strategy cache access (P2-16)
- Truncate raw JSON to 80 chars in override warning (P2-17)
- Warn on non-dict JSON overrides (P2-29)
- Debug log for nesting guard bypass (S13)
- Deep-copy from get() and all_policies() in registry (P1-4)
- Thread-safe registry with threading.Lock (P2-15)
- Robust exception handling in apply_overrides get() (P2-18)
- Log ValidationError details on override failure (P2-19)
- Sanitize service_name via _safe_service_name() (P2-28)
- Warn on non-dict sub-key values in overrides (P2-30)
- Allowlist for is_read_only_plan_operation phases (P2-10)
- Cap retry_auto_debug sleep at 60s (P2-11)
- Use is-not-None instead of falsy checks for error values (P2-12)
- Extend secret regex with bearer, session_id, auth_token,
refresh_token, client_secret patterns (P2-25)
- Pre-truncate error messages to 2000 chars before regex (P2-26)
- Add upper bounds on retry Settings fields (P2-7)
- Add cross-field validator max_delay >= base_delay (P2-8)
- Case-insensitive backoff strategy validation (P2-21)
- Add half_open_max_successes setting (S10)
- Remove phantom ContextFragment from services __all__ (ImportError fix)
- Export ServiceRetryWiring from application.services package
- Include sanitised error context in TypeError logging fallback
- Initialise RetryContext.attempt_count to 1 for bare context-manager usage
- Introduce CircuitBreakerState StrEnum replacing raw string literals
- Fix vacuous CircuitBreakerOpen propagation assertions in BDD steps
- Replace tautological logging test with structlog capture verification
- Assert circuit breaker existence instead of silently skipping on None
- Add Unicode control-char rejection validator to ServiceRetryPolicy.service_name
- Add name parameter with service= in all log calls
- Add extra="forbid" to all 3 Pydantic models
- Deep-copy _SERVICE_DEFAULTS construction
- Key normalisation (.strip()) in get() and apply_overrides()
- Add cooldown <= recovery_timeout validator
- Async guard on RetryContext.execute()
- Nesting guard on RetryContext.execute()/async_execute()
- stop_after_delay(300.0) on RetryContext
- retry_auto_debug async-only guard, dict result fix, sleep guard
- Retry-attempt logging in RetryContext
- Module-level docs for contextlib.suppress(TypeError) rationale
- Exhaustion log on retry failure
- Startup log in __init__; name=service_name to CircuitBreaker
- log_after_retry guarded to not fire on first-attempt success
- get_retry_decorator now includes logging callbacks
- Changed retry_backoff_strategy from str to RetryStrategy StrEnum
Closes#313
This branch contains the fix for bug #592 (actor name slash sanitisation).
The TDD tests from PR #655 are tagged @tdd_expected_fail which expects
the bug to be present, but since the fix is on this branch the tests
pass unexpectedly, causing CI failures.
Remove the @tdd_expected_fail tags from both Behave and Robot test files
so the tests run as normal regression tests alongside the fix.
ISSUES CLOSED: #592
The helper was written before the tdd_expected_fail_listener existed and
inverted pass/fail internally. After merging master the listener is now
active, causing a double inversion that made all three tdd_bug_592 Robot
tests fail with 'bug appears to be fixed — remove the tag'.
Switch to the real-outcome convention: exit 0 + sentinel when the bug is
fixed, exit 1 when the bug is still present, and let the listener handle
inversion.
ISSUES CLOSED: #634
Implement TDD bug-capture tests for bug #570 where `agents session create`
fails because `_get_session_service()` calls `container.db()` which does
not exist on the DI Container class (AttributeError). Same root cause as
bug #554.
Behave BDD scenarios tagged @tdd_bug @tdd_bug_570 @tdd_expected_fail
exercise the real DI path (no mocks). Includes Robot Framework integration
smoke tests with self-inverting helper and ASV benchmark baseline.
ISSUES CLOSED: #631
Temporal metadata fields (validFrom, validUntil, isCurrent, isRevisionOf)
on UKO InformationUnit nodes enable revision chain tracking: when code
changes, old nodes are marked historical and new revision nodes are
created with back-links. Three storage tiers (hot/warm/cold) filter
nodes by temporal scope (current/recent/all) with configurable retention
(warm_retention_hours default 24h, cold_retention_days default 90d).
Includes TemporalMetadata, TemporalNode, RevisionChain, TierQueryResult,
TierRetentionConfig frozen domain models, TemporalBackend protocol,
InMemoryTemporalBackend stub, TemporalService with structlog and DI,
BackendSet.temporal typing upgrade from object|None to TemporalBackend|None.
67 Behave scenarios, 8 Robot Framework tests, ASV benchmarks, and
reference documentation.
ISSUES CLOSED: #577
Bug #554 is NOT yet fixed — the DI container still lacks a db provider.
The previous commit incorrectly removed tdd_expected_fail tags, exposing
the real failures in Behave unit tests (13 scenarios).
Root cause: the Robot helper had its own pass/fail inversion (a workaround
from before tdd_expected_fail_listener existed). The master merge brought
in the listener, causing double inversion that made Robot tests appear to
pass. This commit:
- Rewrites the helper to report real outcomes (exit 0 = bug fixed)
- Restores tdd_expected_fail on all 16 Robot + Behave scenarios
- Lets the listener (Robot) and environment.py (Behave) handle inversion
Implement the UKOIndexer service that produces UKO triples from resources
using pluggable domain-specific analyzers, wraps each triple with provenance
metadata, and simultaneously indexes into text, vector, and graph backends.
Key design decisions and components:
- UKOIndexer orchestrates the full index lifecycle: add_resource,
update_resource (remove-then-add), remove_resource, and maintenance
triggers. Each operation fires lifecycle hooks (on_indexed, on_removed,
on_error) so callers can observe progress.
- Analyzer selection is pluggable via ContentAnalyzer protocol. The indexer
accepts a registry mapping resource types to analyzers. PythonAnalyzer
and MarkdownAnalyzer are provided as built-in implementations.
- LocationContentReader protocol abstracts file I/O with a base_dir
parameter for path-traversal prevention (post-resolve validation rejects
paths escaping the base directory and non-regular files).
- UKOTriple model includes a @model_validator ensuring at least one of
object_uri or object_value is populated, preventing empty triples at
construction time.
- Triple removal uses scoped deletion via uko:sourceResource predicate to
avoid shared-subject collision — only triples originating from the
specific resource are removed, not all triples for a shared subject.
- _resource_subjects.pop is deferred until after all backend removal
operations succeed, preventing inconsistent state on partial failure.
- analyzer.analyze() is wrapped in try/except so that analyzer errors
produce an IndexResult with error details rather than propagating
exceptions to callers.
- All lifecycle hook calls are guarded via _fire_on_indexed,
_fire_on_removed, and _fire_on_error helpers that catch and log hook
exceptions without disrupting the indexing pipeline.
- max_triples parameter (default 50,000) bounds analyzer output size to
prevent runaway resource consumption.
- ResourceFileWatcher monitors filesystem paths via watchdog and triggers
re-indexing callbacks on file changes with configurable debouncing.
Emits RESOURCE_MODIFIED domain events via EventBus when file changes
are detected. Debounce timers coalesce rapid edits into a single
callback invocation. Thread-safe design with daemon threads for clean
shutdown.
- SearchResult.__post_init__ validates score is in [0.0, 1.0], correctly
rejecting NaN values.
- Placeholder embedding uses [1.0] instead of [float(len(content))] to
avoid leaking content size information.
- isinstance check on graph_backend ensures GraphIndexBackend protocol
compliance at runtime.
- Test doubles extracted to features/mocks/uko_indexer_mocks.py for reuse
across BDD steps and Robot helpers.
Spec reference: Architecture > ACMS > Real-time Index Synchronization
(specification.md lines ~43205-43300).
ISSUES CLOSED: #578
The merge from master brought in the DI container fix for bug #554.
All 16 session-list TDD tests now pass, so the tdd_expected_fail
tags are removed per the TDD listener protocol.
ActorRegistry._actor_name() built names via f"{provider}/{model}", which
produced names with multiple slashes when providers included models
containing "/" (e.g. OpenRouter's "anthropic/claude-sonnet-4-20250514").
The resulting name violated the spec pattern ^[a-z0-9_-]+/[a-z0-9_-]+$
and triggered a ValidationError during actor upsert.
Now sanitises both provider and model components by replacing "/" with "-"
and lowercasing, so multi-slash provider models no longer break actor
listing.
Includes 6 Behave BDD regression scenarios (covering zero-provider,
multi-slash, consecutive-slash, leading-slash, and name-validation
cases), Robot Framework integration smoke tests, and ASV benchmarks.
ISSUES CLOSED: #592