diff --git a/.data/snapshots/state_AgentA.json.meta b/.data/snapshots/state_AgentA.json.meta index dfdbfd7..5a62852 100644 --- a/.data/snapshots/state_AgentA.json.meta +++ b/.data/snapshots/state_AgentA.json.meta @@ -1 +1 @@ -{"created_at": "2025-10-10T08:23:58Z", "schema_version": "v1"} +{"created_at": "2025-10-10T10:21:34Z", "schema_version": "v1"} diff --git a/.data/snapshots/state_Ambrose.json.meta b/.data/snapshots/state_Ambrose.json.meta index 4c28666..5a62852 100644 --- a/.data/snapshots/state_Ambrose.json.meta +++ b/.data/snapshots/state_Ambrose.json.meta @@ -1 +1 @@ -{"created_at": "2025-10-10T08:23:57Z", "schema_version": "v1"} +{"created_at": "2025-10-10T10:21:34Z", "schema_version": "v1"} diff --git a/.data/snapshots/state_agent.json.meta b/.data/snapshots/state_agent.json.meta index 56f8530..7145988 100644 --- a/.data/snapshots/state_agent.json.meta +++ b/.data/snapshots/state_agent.json.meta @@ -1 +1 @@ -{"created_at": "2025-10-10T08:23:55Z", "schema_version": "v1"} +{"created_at": "2025-10-10T10:21:31Z", "schema_version": "v1"} diff --git a/.data/snapshots/state_smoke.json.meta b/.data/snapshots/state_smoke.json.meta index dfdbfd7..5a62852 100644 --- a/.data/snapshots/state_smoke.json.meta +++ b/.data/snapshots/state_smoke.json.meta @@ -1 +1 @@ -{"created_at": "2025-10-10T08:23:58Z", "schema_version": "v1"} +{"created_at": "2025-10-10T10:21:34Z", "schema_version": "v1"} diff --git a/.logs/apply.jsonl b/.logs/apply.jsonl index be144ed..0e1ea73 100644 --- a/.logs/apply.jsonl +++ b/.logs/apply.jsonl @@ -1 +1 @@ -{"turn": "demo-1", "agent": "AgentA", "applied": 0, "clamps": 0, "version_etag": "46", "snapshot": "./.data/snapshots/state_AgentA.json", "cache_invalidations": 0, "ms": 0.777} +{"turn": "demo-1", "agent": "AgentA", "applied": 0, "clamps": 0, "version_etag": "46", "snapshot": "./.data/snapshots/state_AgentA.json", "cache_invalidations": 0, "ms": 0.88} diff --git a/.logs/t1.jsonl b/.logs/t1.jsonl index 103f387..a519dfe 100644 --- a/.logs/t1.jsonl +++ b/.logs/t1.jsonl @@ -1 +1 @@ -{"turn": "demo-1", "agent": "AgentA", "pops": 5, "iters": 1, "propagations": 3, "radius_cap_hits": 0, "layer_cap_hits": 0, "node_budget_hits": 0, "max_delta": 1.0, "graphs_touched": 1, "cache_hits": 0, "cache_misses": 1, "cache_used": false, "cache_enabled": true, "ms": 0.278, "now": "2025-10-10T08:23:58.151083+00:00"} +{"turn": "demo-1", "agent": "AgentA", "pops": 5, "iters": 1, "propagations": 3, "radius_cap_hits": 0, "layer_cap_hits": 0, "node_budget_hits": 0, "max_delta": 1.0, "graphs_touched": 1, "cache_hits": 0, "cache_misses": 1, "cache_used": false, "cache_enabled": true, "ms": 0.381, "now": "2025-10-10T10:21:34.762491+00:00"} diff --git a/.logs/t2.jsonl b/.logs/t2.jsonl index b156d97..db31324 100644 --- a/.logs/t2.jsonl +++ b/.logs/t2.jsonl @@ -1 +1 @@ -{"turn": "demo-1", "agent": "AgentA", "tier_sequence": ["exact_semantic", "cluster_semantic", "archive"], "k_returned": 0, "k_used": 0, "k_residual": 0, "sim_stats": {"mean": 0.0, "max": 0.0}, "score_stats": {"mean": 0.0, "max": 0.0}, "owner_scope": "any", "caps": {"residual_cap": 32}, "cache_enabled": true, "cache_used": true, "cache_hits": 0, "cache_misses": 2, "backend": "inmemory", "backend_fallback": false, "hybrid_used": false, "cache_hit": false, "cache_size": 1, "ms": 0.145, "now": "2025-10-10T08:23:58.151083+00:00"} +{"turn": "demo-1", "agent": "AgentA", "tier_sequence": ["exact_semantic", "cluster_semantic", "archive"], "k_returned": 0, "k_used": 0, "k_residual": 0, "sim_stats": {"mean": 0.0, "max": 0.0}, "score_stats": {"mean": 0.0, "max": 0.0}, "owner_scope": "any", "caps": {"residual_cap": 32}, "cache_enabled": true, "cache_used": true, "cache_hits": 0, "cache_misses": 2, "backend": "inmemory", "backend_fallback": false, "hybrid_used": false, "cache_hit": false, "cache_size": 1, "ms": 0.126, "now": "2025-10-10T10:21:34.762491+00:00"} diff --git a/.logs/t3.jsonl b/.logs/t3.jsonl index a40f842..13bd6e3 100644 --- a/.logs/t3.jsonl +++ b/.logs/t3.jsonl @@ -1 +1 @@ -{"turn": "demo-1", "agent": "AgentA", "backend": "rulebased", "ops_counts": {"Speak": 1, "RequestRetrieve": 1}, "requested_retrieve": true, "rag_used": true, "ms_plan": 0.032, "ms_rag": 0.055, "ms_speak": 0.036, "now": "2025-10-10T08:23:58.151083+00:00"} +{"turn": "demo-1", "agent": "AgentA", "backend": "rulebased", "ops_counts": {"Speak": 1, "RequestRetrieve": 1}, "requested_retrieve": true, "rag_used": true, "ms_plan": 0.029, "ms_rag": 0.053, "ms_speak": 0.038, "now": "2025-10-10T10:21:34.762491+00:00"} diff --git a/.logs/t3_dialogue.jsonl b/.logs/t3_dialogue.jsonl index a17e739..1cb5d01 100644 --- a/.logs/t3_dialogue.jsonl +++ b/.logs/t3_dialogue.jsonl @@ -1 +1 @@ -{"turn": "demo-1", "agent": "AgentA", "tokens": 7, "truncated": false, "style_prefix_used": false, "snippet_count": 0, "ms": 0.036, "backend": "rulebased", "now": "2025-10-10T08:23:58.151083+00:00"} +{"turn": "demo-1", "agent": "AgentA", "tokens": 7, "truncated": false, "style_prefix_used": false, "snippet_count": 0, "ms": 0.038, "backend": "rulebased", "now": "2025-10-10T10:21:34.762491+00:00"} diff --git a/.logs/t3_plan.jsonl b/.logs/t3_plan.jsonl index 48e7b80..17164f6 100644 --- a/.logs/t3_plan.jsonl +++ b/.logs/t3_plan.jsonl @@ -1 +1 @@ -{"turn": "demo-1", "agent": "AgentA", "policy_backend": "rulebased", "backend": "rulebased", "ops_counts": {"Speak": 1, "RequestRetrieve": 1}, "requested_retrieve": true, "rag_used": true, "reflection": false, "ms_deliberate": 0.032, "ms_rag": 0.055, "now": "2025-10-10T08:23:58.151083+00:00"} +{"turn": "demo-1", "agent": "AgentA", "policy_backend": "rulebased", "backend": "rulebased", "ops_counts": {"Speak": 1, "RequestRetrieve": 1}, "requested_retrieve": true, "rag_used": true, "reflection": false, "ms_deliberate": 0.029, "ms_rag": 0.053, "now": "2025-10-10T10:21:34.762491+00:00"} diff --git a/.logs/t4.jsonl b/.logs/t4.jsonl index cba737c..d370cc4 100644 --- a/.logs/t4.jsonl +++ b/.logs/t4.jsonl @@ -1 +1 @@ -{"turn": "demo-1", "agent": "AgentA", "counts": {"input": 0, "after_cooldown": 0, "after_novelty": 0, "after_l2": 0, "approved": 0, "dropped_tail": 0}, "clamps": {"novelty_clamped": 0, "l2_scale": 1.0}, "cooldowns": {"blocked_ops": 0}, "caps": {"delta_norm_cap_l2": 1.5, "novelty_cap_per_node": 0.3, "churn_cap_edges": 64}, "approved": 0, "rejected": 0, "reasons": [], "ms": 0.006, "now": "2025-10-10T08:23:58.151083+00:00"} +{"turn": "demo-1", "agent": "AgentA", "counts": {"input": 0, "after_cooldown": 0, "after_novelty": 0, "after_l2": 0, "approved": 0, "dropped_tail": 0}, "clamps": {"novelty_clamped": 0, "l2_scale": 1.0}, "cooldowns": {"blocked_ops": 0}, "caps": {"delta_norm_cap_l2": 1.5, "novelty_cap_per_node": 0.3, "churn_cap_edges": 64}, "approved": 0, "rejected": 0, "reasons": [], "ms": 0.006, "now": "2025-10-10T10:21:34.762491+00:00"} diff --git a/.logs/turn.jsonl b/.logs/turn.jsonl index da59683..802719a 100644 --- a/.logs/turn.jsonl +++ b/.logs/turn.jsonl @@ -1 +1 @@ -{"turn": "demo-1", "agent": "AgentA", "durations_ms": {"t1": 0.278, "t2": 0.145, "t4": 0.006, "apply": 0.777, "total": 2.955}, "t1": {"pops": 5, "iters": 1, "graphs_touched": 1}, "t2": {"k_returned": 0, "k_used": 0, "cache_hit": false}, "t4": {"approved": 0, "rejected": 0}, "now": "2025-10-10T08:23:58.151083+00:00"} +{"turn": "demo-1", "agent": "AgentA", "durations_ms": {"t1": 0.381, "t2": 0.126, "t4": 0.006, "apply": 0.88, "total": 3.376}, "t1": {"pops": 5, "iters": 1, "graphs_touched": 1}, "t2": {"k_returned": 0, "k_used": 0, "cache_hit": false}, "t4": {"approved": 0, "rejected": 0}, "now": "2025-10-10T10:21:34.762491+00:00"} diff --git a/clematis/VERSION b/clematis/VERSION index 5eef0f1..a3f5a8e 100644 --- a/clematis/VERSION +++ b/clematis/VERSION @@ -1 +1 @@ -0.10.2 +0.10.3 diff --git a/clematis/engine/types.py b/clematis/engine/types.py index 2911163..8045edd 100644 --- a/clematis/engine/types.py +++ b/clematis/engine/types.py @@ -289,7 +289,7 @@ class Config: } ) flags: Dict[str, Any] = field( - default_factory=lambda: {"enable_world_memory": True, "allow_reflection": True} + default_factory=lambda: {"enable_world_memory": False, "allow_reflection": False} ) # M5: scheduler config (feature-flagged; defaults merged by validate.py) diff --git a/clematis/errors.py b/clematis/errors.py index 8f85a0c..46819da 100644 --- a/clematis/errors.py +++ b/clematis/errors.py @@ -28,18 +28,13 @@ class ClematisError(Exception): -# NOTE(backcompat): For v3 we also inherit from ValueError so older tests/consumers -# that catch ValueError keep working. -# TODO(v4): Drop ValueError parent and migrate callers to catch ConfigError/ClematisError. -class ConfigError(ClematisError, ValueError): +# Typed errors no longer inherit from ValueError; callers should catch the specific subclasses. +class ConfigError(ClematisError): """Configuration invalid, unknown keys, wrong version, etc.""" pass - -# NOTE(backcompat): For v3 we also inherit from ValueError for legacy callers/tests. -# TODO(v4): Drop ValueError parent and migrate callers to catch SnapshotError/ClematisError. -class SnapshotError(ClematisError, ValueError): +class SnapshotError(ClematisError): """Snapshot missing schema, mismatched version, corrupted header, etc.""" pass diff --git a/clematis/scripts/validate.py b/clematis/scripts/validate.py index 91608f6..3751e09 100644 --- a/clematis/scripts/validate.py +++ b/clematis/scripts/validate.py @@ -25,6 +25,15 @@ except Exception: # PyYAML optional yaml = None # type: ignore +try: + from clematis.errors import ConfigError +except ModuleNotFoundError: + HERE = os.path.abspath(os.path.dirname(__file__)) + ROOT = os.path.abspath(os.path.join(HERE, "..")) + if ROOT not in sys.path: + sys.path.insert(0, ROOT) + from clematis.errors import ConfigError + # Ensure the project root (parent of scripts/) is importable when run directly try: from configs.validate import validate_config_verbose, validate_config # type: ignore @@ -127,8 +136,8 @@ def main(argv: list[str]) -> int: else: normalized = validate_config(cfg) warnings = [] - except ValueError as ve: - print("CONFIG INVALID\n" + str(ve)) + except ConfigError as err: + print("CONFIG INVALID\n" + str(err)) return 1 # --strict: treat warnings as errors diff --git a/configs/config.yaml b/configs/config.yaml index 802d7e6..2f292b1 100644 --- a/configs/config.yaml +++ b/configs/config.yaml @@ -1,3 +1,4 @@ +version: "v1" k_surface: 32 surface_method: PCA t1: @@ -48,10 +49,10 @@ t3: tokens: 256 temp: 0.2 max_ops_per_turn: 3 - allow_reflection: true - backend: llm + allow_reflection: false + backend: rulebased reflection: - backend: llm # deterministic summariser; "llm" requires fixtures (see PR84) + backend: rulebased # deterministic summariser; switch to "llm" via overlay summary_tokens: 128 # whitespace-tokenised cap embed: true # embed the reflection summary deterministically log: true # write t3_reflection.jsonl (not part of identity logs) @@ -65,15 +66,15 @@ t3: epsilon_edit: 0.10 llm: # M3-07 LLM adapter scaffolding — defaults OFF and fixture-driven in CI - provider: ollama # "fixture" | "ollama" + provider: fixture # "fixture" | "ollama" model: qwen3:4b-instruct endpoint: http://localhost:11434/api/generate max_tokens: 256 temp: 0.2 timeout_ms: 10000 fixtures: - enabled: true # fixtures-only mode for deterministic LLM reflection - path: tests/fixtures/llm_cassettes/reflection.jsonl + enabled: false # enable in overlays/examples; requires non-empty path + path: null t4: enabled: true delta_norm_cap_l2: 1.5 @@ -94,7 +95,9 @@ t4: ttl_sec: 600 # budgets: ... budgets: {time_ms: 1000, ops: 1000, tokens: 1024, time_ms_reflection: 6000} -flags: {enable_world_memory: true, allow_reflection: true} +flags: + enable_world_memory: false + allow_reflection: false # ----------------------------------------------------------------------------- # M9 Deterministic Parallelism (defaults OFF) diff --git a/dist_local/clematis-0.10.3-py3-none-any.whl b/dist_local/clematis-0.10.3-py3-none-any.whl new file mode 100644 index 0000000..482be2e Binary files /dev/null and b/dist_local/clematis-0.10.3-py3-none-any.whl differ diff --git a/dist_local/numpy-2.3.3-cp313-cp313-macosx_14_0_arm64.whl b/dist_local/numpy-2.3.3-cp313-cp313-macosx_14_0_arm64.whl new file mode 100644 index 0000000..6ebb5d4 Binary files /dev/null and b/dist_local/numpy-2.3.3-cp313-cp313-macosx_14_0_arm64.whl differ diff --git a/dist_local/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl b/dist_local/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl new file mode 100644 index 0000000..84a9022 Binary files /dev/null and b/dist_local/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl differ diff --git a/docs/m13/error_taxonomy.md b/docs/m13/error_taxonomy.md index 35489d9..b7e91f7 100644 --- a/docs/m13/error_taxonomy.md +++ b/docs/m13/error_taxonomy.md @@ -44,3 +44,4 @@ When an error has an empty message, `format_error(e)` renders only the class nam **Notes** - Error messages are deterministic and concise; avoid stack traces in operator paths. - Identity is preserved in v3; enabling quality/perf/reflection features may change behavior and should remain off by default. +- Typed errors inherit only from `ClematisError`; update callers to catch the specific subclasses instead of `ValueError`. diff --git a/docs/m3/llm_adapter.md b/docs/m3/llm_adapter.md index adcfee8..8517462 100644 --- a/docs/m3/llm_adapter.md +++ b/docs/m3/llm_adapter.md @@ -145,7 +145,7 @@ pytest -q Ensure each line is valid JSON; no trailing commas; UTF‑8 encoding. - **`LLMAdapterError: No fixture for prompt hash`** The prompt string used at runtime must match the one hashed in the JSONL (after newline normalization). -- **Config validation failures (ValueError)** +- **Config validation failures (ConfigError)** - `t3.llm.provider` must be one of `{fixture, ollama}` - `t3.llm.temp` in `[0, 1]` - `t3.llm.max_tokens ≥ 1` diff --git a/healthcheck.md b/healthcheck.md new file mode 100644 index 0000000..8251df6 --- /dev/null +++ b/healthcheck.md @@ -0,0 +1,31 @@ +# Clematis v3 Healthcheck + +## Repository Reality & Dossier Update +- Version metadata now consistently reports **0.10.3** across `pyproject.toml`, `clematis/VERSION`, `clematis.egg-info/PKG-INFO`, CLI man pages, and help goldens (`tests/cli/goldens/help/top.txt`). +- Deterministic turn loop (T1→T4) and staging remain intact; identity CI continues to rely on fixed env vars and normalized LF logs. GEL (`graph.enabled`) and deterministic parallelism (`perf.parallel`) are present but default **OFF**. +- Config schema v1 is enforced via `configs/validate.py`; snapshots remain schema v1 with strict inspector semantics. CLI (`python -m clematis …`) still emits one-line typed errors. +- Default config (`configs/config.yaml`) now mirrors the identity baseline: `t3.allow_reflection=false`, `t3.backend=rulebased`, LLM fixtures disabled, and `flags.allow_reflection=false`. Optional features stay opt-in through overlays/examples. + +## Observed Gaps (carried into v4 planning) +- Native kernels, GEL-informed nudge planner, retrieval quality defaults, and CLI/frontend evolution remain unimplemented; existing scaffolding is deterministic but dormant. + +## Hardening & Maintenance Steps Completed +- Audited defaults and flipped identity gates OFF by editing `configs/config.yaml` (reflection + LLM fixtures now opt-in; added explicit `version: "v1"` header). +- Updated `clematis/engine/types.Config.flags` to default both `enable_world_memory` and `allow_reflection` to `False` to keep in-process defaults consistent with config files and docs. +- Validated the adjusted config with `python3 -m clematis validate -- --config configs/config.yaml` (passes with reflection/quality gates reported as disabled). +- Bumped project version markers to 0.10.3 and refreshed CLI artifacts: updated `pyproject.toml`, `clematis/VERSION`, `clematis.egg-info/PKG-INFO`, `man/*.1`, and `tests/cli/goldens/help/top.txt`. +- Identity marker suite verified (`pytest -q -m identity`), and full test suite passes locally (`pytest -q`). +- Built wheels into `dist_local/` and captured deterministic hashes via `shasum -a 256 dist_local/* | sort` as part of the packaging parity check. +- Removed `ValueError` inheritance from typed errors, updated validators/scripts/tests to catch `ConfigError`/`SnapshotError`, and refreshed docs to match the cleaned taxonomy. + +## Recommended Next Actions +- Re-run the identity and packaging matrices once the version/doc alignment is settled, ensuring the refreshed defaults keep byte-identical outputs. +- Keep overlays/examples (e.g., `examples/reflection/*.yaml`, GEL demos) up to date with the new defaults so operators enable gates explicitly. +- For v4 kickoff, formalize the migration doc covering schema v2, error taxonomy cleanup, native acceleration strategy, and GEL-driven planner roadmap. + +## Suggested Verification Commands +- Run in order: `python3 -m clematis validate -- --config configs/config.yaml` +- `pytest -q -m identity` *(sequential vs parallel/disabled-path matches)* +- `pytest -q tests/config/test_validate_reflection.py` *(reflection gate defaults + fixtures contract)* +- `python3 -m pip wheel . -w dist_local` **then** `shasum -a 256 dist_local/* | sort` +- `python3 -m clematis --help > /tmp/help.txt` and compare against `tests/cli/goldens/help/top.txt` diff --git a/man/clematis-bench-t4.1 b/man/clematis-bench-t4.1 index 8f2f94c..b2355db 100644 --- a/man/clematis-bench-t4.1 +++ b/man/clematis-bench-t4.1 @@ -1,4 +1,4 @@ -.TH clematis-bench-t4 1 "2024-01-01" "Clematis 0.10.2" "User Commands" +.TH clematis-bench-t4 1 "2024-01-01" "Clematis 0.10.3" "User Commands" .SH NAME clematis\-bench\-t4 \\\- Delegates to scripts/ for 'bench\-t4' .SH SYNOPSIS diff --git a/man/clematis-chat.1 b/man/clematis-chat.1 index 617430f..e6d7708 100644 --- a/man/clematis-chat.1 +++ b/man/clematis-chat.1 @@ -1,4 +1,4 @@ -.TH clematis-chat 1 "2024-01-01" "Clematis 0.10.2" "User Commands" +.TH clematis-chat 1 "2024-01-01" "Clematis 0.10.3" "User Commands" .SH NAME clematis\-chat \\\- Delegates to scripts/ for 'chat' .SH SYNOPSIS diff --git a/man/clematis-console.1 b/man/clematis-console.1 index 156d36f..f12c406 100644 --- a/man/clematis-console.1 +++ b/man/clematis-console.1 @@ -1,4 +1,4 @@ -.TH clematis-console 1 "2024-01-01" "Clematis 0.10.2" "User Commands" +.TH clematis-console 1 "2024-01-01" "Clematis 0.10.3" "User Commands" .SH NAME clematis\-console \\\- Delegates to scripts/ for 'console' .SH SYNOPSIS diff --git a/man/clematis-demo.1 b/man/clematis-demo.1 index cdd0834..d12d931 100644 --- a/man/clematis-demo.1 +++ b/man/clematis-demo.1 @@ -1,4 +1,4 @@ -.TH clematis-demo 1 "2024-01-01" "Clematis 0.10.2" "User Commands" +.TH clematis-demo 1 "2024-01-01" "Clematis 0.10.3" "User Commands" .SH NAME clematis\-demo \\\- Delegates to scripts/ for 'demo' .SH SYNOPSIS diff --git a/man/clematis-export-logs.1 b/man/clematis-export-logs.1 index a4d3ed9..3f537e6 100644 --- a/man/clematis-export-logs.1 +++ b/man/clematis-export-logs.1 @@ -1,4 +1,4 @@ -.TH clematis-export-logs 1 "2024-01-01" "Clematis 0.10.2" "User Commands" +.TH clematis-export-logs 1 "2024-01-01" "Clematis 0.10.3" "User Commands" .SH NAME clematis\-export\-logs \\\- Delegates to scripts/ for 'export\-logs' .SH SYNOPSIS diff --git a/man/clematis-inspect-snapshot.1 b/man/clematis-inspect-snapshot.1 index 2ba6f65..5d8ed60 100644 --- a/man/clematis-inspect-snapshot.1 +++ b/man/clematis-inspect-snapshot.1 @@ -1,4 +1,4 @@ -.TH clematis-inspect-snapshot 1 "2024-01-01" "Clematis 0.10.2" "User Commands" +.TH clematis-inspect-snapshot 1 "2024-01-01" "Clematis 0.10.3" "User Commands" .SH NAME clematis\-inspect\-snapshot \\\- Delegates to scripts/ for 'inspect\-snapshot' .SH SYNOPSIS diff --git a/man/clematis-rotate-logs.1 b/man/clematis-rotate-logs.1 index 868c790..b84c19c 100644 --- a/man/clematis-rotate-logs.1 +++ b/man/clematis-rotate-logs.1 @@ -1,4 +1,4 @@ -.TH clematis-rotate-logs 1 "2024-01-01" "Clematis 0.10.2" "User Commands" +.TH clematis-rotate-logs 1 "2024-01-01" "Clematis 0.10.3" "User Commands" .SH NAME clematis\-rotate\-logs \\\- Delegates to scripts/ for 'rotate\-logs' .SH SYNOPSIS diff --git a/man/clematis-seed-lance-demo.1 b/man/clematis-seed-lance-demo.1 index 9e4867b..b099feb 100644 --- a/man/clematis-seed-lance-demo.1 +++ b/man/clematis-seed-lance-demo.1 @@ -1,4 +1,4 @@ -.TH clematis-seed-lance-demo 1 "2024-01-01" "Clematis 0.10.2" "User Commands" +.TH clematis-seed-lance-demo 1 "2024-01-01" "Clematis 0.10.3" "User Commands" .SH NAME clematis\-seed\-lance\-demo \\\- Delegates to scripts/ for 'seed\-lance\-demo' .SH SYNOPSIS diff --git a/man/clematis-validate.1 b/man/clematis-validate.1 index b198e83..970c4eb 100644 --- a/man/clematis-validate.1 +++ b/man/clematis-validate.1 @@ -1,4 +1,4 @@ -.TH clematis-validate 1 "2024-01-01" "Clematis 0.10.2" "User Commands" +.TH clematis-validate 1 "2024-01-01" "Clematis 0.10.3" "User Commands" .SH NAME clematis\-validate \\\- Delegates to scripts/ for 'validate' .SH SYNOPSIS diff --git a/man/clematis.1 b/man/clematis.1 index b95067b..2f01396 100644 --- a/man/clematis.1 +++ b/man/clematis.1 @@ -1,4 +1,4 @@ -.TH clematis 1 "2024-01-01" "Clematis 0.10.2" "User Commands" +.TH clematis 1 "2024-01-01" "Clematis 0.10.3" "User Commands" .SH NAME clematis \\\- umbrella CLI for Clematis .SH SYNOPSIS @@ -9,7 +9,7 @@ umbrella CLI for Clematis .nf usage: clematis [\-h] [\-\-version] ... -Clematis umbrella CLI (v0.10.2) +Clematis umbrella CLI (v0.10.3) options: \-h, \-\-help show this help message and exit diff --git a/pyproject.toml b/pyproject.toml index e14aa1e..2830489 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "clematis" -version = "0.10.2" +version = "0.10.3" description = "Clematis — deterministic mind-like simulation engine (M1–M8 baseline)" readme = "README.md" requires-python = ">=3.11,<3.14" diff --git a/scripts/examples_smoke.py b/scripts/examples_smoke.py index ae21268..cae29b1 100644 --- a/scripts/examples_smoke.py +++ b/scripts/examples_smoke.py @@ -32,6 +32,8 @@ except Exception: # Best-effort; fall back to user-provided PYTHONPATH or editable install pass + +from clematis.errors import ConfigError from typing import Any, Dict, Iterable, List, Mapping, Optional, Sequence, Tuple # ------------------------------ imports ------------------------------------ @@ -138,7 +140,7 @@ def _run_example( cfg = _load_yaml(path) try: normalized, warnings = validate_config_verbose(cfg) - except ValueError as e: + except ConfigError as e: return False, f"validation errors: {str(e)}" cfg = normalized diff --git a/scripts/validate_config.py b/scripts/validate_config.py index e485514..feb0000 100644 --- a/scripts/validate_config.py +++ b/scripts/validate_config.py @@ -31,6 +31,8 @@ if ROOT not in sys.path: sys.path.insert(0, ROOT) +from clematis.errors import ConfigError + def _load_validator(): """Import the configs.validate module lazily to avoid circular imports. @@ -135,8 +137,8 @@ def main(argv: list[str]) -> int: else: normalized = validate_config_func(cfg) warnings = [] - except ValueError as ve: - print("CONFIG INVALID\n" + str(ve)) + except ConfigError as err: + print("CONFIG INVALID\n" + str(err)) return 1 # --strict: treat warnings as errors diff --git a/tests/cli/goldens/help/top.txt b/tests/cli/goldens/help/top.txt index 05be092..260d1f7 100644 --- a/tests/cli/goldens/help/top.txt +++ b/tests/cli/goldens/help/top.txt @@ -1,6 +1,6 @@ usage: clematis [-h] [--version] -Clematis umbrella CLI (v0.10.2) +Clematis umbrella CLI (v0.10.3) options: -h, --help show this help message and exit diff --git a/tests/config/test_config_version_lock.py b/tests/config/test_config_version_lock.py index e30483a..e6d72ac 100644 --- a/tests/config/test_config_version_lock.py +++ b/tests/config/test_config_version_lock.py @@ -1,10 +1,10 @@ from __future__ import annotations - import pytest from configs.validate import validate_config, CONFIG_VERSION +from clematis.errors import ConfigError def _base_cfg() -> dict: @@ -28,7 +28,7 @@ def test_accepts_v1_version(): def test_rejects_wrong_version(): cfg = _base_cfg() cfg["version"] = "v999" - with pytest.raises(ValueError) as e: + with pytest.raises(ConfigError) as e: validate_config(cfg) # Message should be explicit but avoid overfitting exact text assert "must be" in str(e.value) @@ -37,5 +37,5 @@ def test_rejects_wrong_version(): def test_rejects_unknown_top_level_keys(): cfg = _base_cfg() cfg["whaaat"] = {} - with pytest.raises(ValueError): + with pytest.raises(ConfigError): validate_config(cfg) diff --git a/tests/config/test_validate_perf_snapshots.py b/tests/config/test_validate_perf_snapshots.py index 89f875a..203cbd4 100644 --- a/tests/config/test_validate_perf_snapshots.py +++ b/tests/config/test_validate_perf_snapshots.py @@ -1,4 +1,5 @@ import pytest +from clematis.errors import ConfigError try: from configs.validate import validate_config, validate_config_verbose @@ -34,7 +35,7 @@ def test_rejects_bad_level_and_codec(): }, } } - with pytest.raises(ValueError) as e: + with pytest.raises(ConfigError) as e: validate_config(cfg) msg = str(e.value).lower() assert "perf.snapshots.compression" in msg or "compression" in msg diff --git a/tests/config/test_validate_reflection.py b/tests/config/test_validate_reflection.py index a3b89df..d097ee9 100644 --- a/tests/config/test_validate_reflection.py +++ b/tests/config/test_validate_reflection.py @@ -4,6 +4,7 @@ import pytest import importlib +from clematis.errors import ConfigError _val_mod = importlib.import_module("configs.validate") for _name in ("validate", "validate_and_normalize", "validate_config", "validate_cfg"): if hasattr(_val_mod, _name): @@ -76,7 +77,7 @@ def test_types_enforced_and_nonneg_ints(): def test_ops_reflection_negative_raises(): cfg = base_cfg() cfg["scheduler"]["budgets"]["ops_reflection"] = -1 - with pytest.raises(ValueError): + with pytest.raises(ConfigError): _validate(copy.deepcopy(cfg)) @pytest.mark.parametrize("coerce_in", ["5", 3.14]) @@ -101,12 +102,12 @@ def test_unknown_key_rejected_in_reflection(): "surprise": 42, # not allowed }, } - with pytest.raises(ValueError): + with pytest.raises(ConfigError): _validate(copy.deepcopy(cfg)) def test_backend_choice_restricted(): cfg = base_cfg() cfg["t3"] = {"allow_reflection": True, "reflection": {"backend": "nope"}} - with pytest.raises(ValueError): + with pytest.raises(ConfigError): _validate(copy.deepcopy(cfg)) diff --git a/tests/reflection/test_reflection_llm_requires_fixtures.py b/tests/reflection/test_reflection_llm_requires_fixtures.py index 33f1572..1e8b506 100644 --- a/tests/reflection/test_reflection_llm_requires_fixtures.py +++ b/tests/reflection/test_reflection_llm_requires_fixtures.py @@ -1,6 +1,7 @@ import pytest from configs.validate import validate_config +from clematis.errors import ConfigError def test_llm_requires_fixtures_enabled(): @@ -18,7 +19,7 @@ def test_llm_requires_fixtures_enabled(): }, "scheduler": {"budgets": {"time_ms_reflection": 6000, "ops_reflection": 5}}, } - with pytest.raises(ValueError): + with pytest.raises(ConfigError): validate_config(cfg) @@ -37,5 +38,5 @@ def test_llm_requires_path_when_enabled(): }, "scheduler": {"budgets": {"time_ms_reflection": 6000, "ops_reflection": 5}}, } - with pytest.raises(ValueError): + with pytest.raises(ConfigError): validate_config(cfg) diff --git a/tests/test_config_perf_quality.py b/tests/test_config_perf_quality.py index 4cf2e54..9583074 100644 --- a/tests/test_config_perf_quality.py +++ b/tests/test_config_perf_quality.py @@ -6,6 +6,8 @@ import pytest +from clematis.errors import ConfigError + # Import the validator(s) under test from configs.validate import validate_config, validate_config_verbose @@ -87,7 +89,7 @@ def test_quality_prep_inert_variations(): def test_perf_validation_bounds_and_types(): """ - perf.* accepts only bounded values; invalid settings raise ValueError with precise paths. + perf.* accepts only bounded values; invalid settings raise ConfigError with precise paths. """ bads = [ ({"perf": {"t1": {"queue_cap": 0}}}, "perf.t1.queue_cap"), @@ -101,7 +103,7 @@ def test_perf_validation_bounds_and_types(): ({"perf": {"snapshots": {"every_n_turns": 0}}}, "perf.snapshots.every_n_turns"), ] for cfg, needle in bads: - with pytest.raises(ValueError) as ei: + with pytest.raises(ConfigError) as ei: validate_config(cfg) assert needle in str(ei.value) @@ -124,7 +126,7 @@ def test_warnings_fp16_without_norms_and_kfinal_gt_k(): def test_quality_value_bounds_and_types_errors(): """ - Certain invalid values in quality subkeys should raise validation errors even if enabled:false. + Certain invalid values in quality subkeys should raise ConfigError even if enabled:false. (Bounds/type checks are independent of wiring.) """ bads = [ @@ -145,7 +147,7 @@ def test_quality_value_bounds_and_types_errors(): ({"t2": {"quality": {"mmr": {"k_final": 0}}}}, "t2.quality.mmr.k_final"), ] for cfg, needle in bads: - with pytest.raises(ValueError) as ei: + with pytest.raises(ConfigError) as ei: validate_config(cfg) assert needle in str(ei.value) diff --git a/tests/test_config_validate.py b/tests/test_config_validate.py index c7b9d5f..b24c759 100644 --- a/tests/test_config_validate.py +++ b/tests/test_config_validate.py @@ -1,6 +1,7 @@ import pytest from configs.validate import validate_config, validate_config_verbose +from clematis.errors import ConfigError def test_validate_happy_defaults(): @@ -25,7 +26,7 @@ def test_validate_happy_defaults(): ], ) def test_validate_raises_on_bad_values(bad_cfg, needle): - with pytest.raises(ValueError) as ei: + with pytest.raises(ConfigError) as ei: validate_config(bad_cfg) msg = str(ei.value) assert needle in msg @@ -41,7 +42,7 @@ def test_t4_cache_ttl_alias_and_priority(): def test_t4_cache_namespaces_must_be_list_of_str(): - with pytest.raises(ValueError) as ei: + with pytest.raises(ConfigError) as ei: validate_config({"t4": {"cache": {"namespaces": "oops"}}}) assert "t4.cache.namespaces" in str(ei.value) @@ -73,7 +74,7 @@ def test_t2_backend_enum_and_threshold(): out = validate_config({"t2": {"backend": "inmemory", "sim_threshold": 0.0}}) assert out["t2"]["backend"] == "inmemory" # bad backend - with pytest.raises(ValueError) as ei: + with pytest.raises(ConfigError) as ei: validate_config({"t2": {"backend": "duckdb"}}) assert "t2.backend" in str(ei.value) @@ -83,7 +84,7 @@ def test_t4_cooldowns_nonnegative_and_string_keys(): out = validate_config({"t4": {"cooldowns": {"EditGraph": 2, "CreateGraph": 0}}}) assert out["t4"]["cooldowns"]["EditGraph"] == 2 # negative value - with pytest.raises(ValueError) as ei: + with pytest.raises(ConfigError) as ei: validate_config({"t4": {"cooldowns": {"EditGraph": -1}}}) assert "t4.cooldowns[EditGraph]" in str(ei.value) @@ -92,7 +93,7 @@ def test_t4_cooldowns_nonnegative_and_string_keys(): def test_unknown_key_suggestion(): # Typo should raise with a suggestion in the error message bad = {"t2": {"backnd": "inmemory"}} - with pytest.raises(ValueError) as ei: + with pytest.raises(ConfigError) as ei: validate_config(bad) msg = str(ei.value) assert "t2.backnd" in msg @@ -123,7 +124,7 @@ def test_duplicate_cache_namespace_warns_in_verbose(): def test_t4_cache_namespace_membership(): # Invalid namespace should error with allowed set indicated cfg = {"t4": {"cache": {"namespaces": ["t2:semantics"]}}} - with pytest.raises(ValueError) as ei: + with pytest.raises(ConfigError) as ei: validate_config(cfg) msg = str(ei.value) assert "t4.cache.namespaces[t2:semantics]" in msg @@ -133,14 +134,14 @@ def test_t4_cache_namespace_membership(): @pytest.mark.parametrize("bad", [1.2, -1.2]) def test_t2_sim_threshold_bounds(bad): cfg = {"t2": {"sim_threshold": bad}} - with pytest.raises(ValueError) as ei: + with pytest.raises(ConfigError) as ei: validate_config(cfg) assert "t2.sim_threshold" in str(ei.value) def test_snapshot_every_n_turns_must_be_positive(): cfg = {"t4": {"snapshot_every_n_turns": 0, "cache": {"namespaces": ["t2:semantic"]}}} - with pytest.raises(ValueError) as ei: + with pytest.raises(ConfigError) as ei: validate_config(cfg) assert "t4.snapshot_every_n_turns" in str(ei.value) @@ -163,19 +164,19 @@ def test_graph_merge_defaults_and_bounds(): def test_graph_merge_invalid_values(): # min_size < 2 - with pytest.raises(ValueError) as ei: + with pytest.raises(ConfigError) as ei: validate_config({"graph": {"merge": {"min_size": 1}}}) assert "graph.merge.min_size" in str(ei.value) # min_avg_w out of range - with pytest.raises(ValueError) as ei: + with pytest.raises(ConfigError) as ei: validate_config({"graph": {"merge": {"min_avg_w": 1.5}}}) assert "graph.merge.min_avg_w" in str(ei.value) # max_diameter < 1 - with pytest.raises(ValueError) as ei: + with pytest.raises(ConfigError) as ei: validate_config({"graph": {"merge": {"max_diameter": 0}}}) assert "graph.merge.max_diameter" in str(ei.value) # cap_per_turn negative - with pytest.raises(ValueError) as ei: + with pytest.raises(ConfigError) as ei: validate_config({"graph": {"merge": {"cap_per_turn": -1}}}) assert "graph.merge.cap_per_turn" in str(ei.value) @@ -191,15 +192,15 @@ def test_graph_split_defaults_and_bounds(): def test_graph_split_invalid_values(): # weak_edge_thresh out of range - with pytest.raises(ValueError) as ei: + with pytest.raises(ConfigError) as ei: validate_config({"graph": {"split": {"weak_edge_thresh": -0.1}}}) assert "graph.split.weak_edge_thresh" in str(ei.value) # min_component_size < 2 - with pytest.raises(ValueError) as ei: + with pytest.raises(ConfigError) as ei: validate_config({"graph": {"split": {"min_component_size": 1}}}) assert "graph.split.min_component_size" in str(ei.value) # cap_per_turn negative - with pytest.raises(ValueError) as ei: + with pytest.raises(ConfigError) as ei: validate_config({"graph": {"split": {"cap_per_turn": -2}}}) assert "graph.split.cap_per_turn" in str(ei.value) @@ -216,19 +217,19 @@ def test_graph_promotion_defaults_and_bounds(): def test_graph_promotion_invalid_values(): # bad label_mode - with pytest.raises(ValueError) as ei: + with pytest.raises(ConfigError) as ei: validate_config({"graph": {"promotion": {"label_mode": "weird"}}}) assert "graph.promotion.label_mode" in str(ei.value) # topk_label_ids < 1 - with pytest.raises(ValueError) as ei: + with pytest.raises(ConfigError) as ei: validate_config({"graph": {"promotion": {"topk_label_ids": 0}}}) assert "graph.promotion.topk_label_ids" in str(ei.value) # attach_weight out of range - with pytest.raises(ValueError) as ei: + with pytest.raises(ConfigError) as ei: validate_config({"graph": {"promotion": {"attach_weight": 2}}}) assert "graph.promotion.attach_weight" in str(ei.value) # cap_per_turn negative - with pytest.raises(ValueError) as ei: + with pytest.raises(ConfigError) as ei: validate_config({"graph": {"promotion": {"cap_per_turn": -1}}}) assert "graph.promotion.cap_per_turn" in str(ei.value) @@ -241,7 +242,7 @@ def test_graph_cross_field_consistency(): "split": {"weak_edge_thresh": 0.50}, } } - with pytest.raises(ValueError) as ei: + with pytest.raises(ConfigError) as ei: validate_config(bad) msg = str(ei.value) assert "graph.split.weak_edge_thresh" in msg @@ -250,7 +251,7 @@ def test_graph_cross_field_consistency(): def test_graph_unknown_keys_under_promotion(): bad = {"graph": {"promotion": {"speed": 1}}} - with pytest.raises(ValueError) as ei: + with pytest.raises(ConfigError) as ei: validate_config(bad) msg = str(ei.value) assert "graph.promotion.speed" in msg diff --git a/tests/test_scheduler_config_validate.py b/tests/test_scheduler_config_validate.py index beca598..0ef70c2 100644 --- a/tests/test_scheduler_config_validate.py +++ b/tests/test_scheduler_config_validate.py @@ -6,6 +6,7 @@ validate_config, validate_config_verbose, ) +from clematis.errors import ConfigError def _expect_valid(user_cfg): @@ -16,8 +17,8 @@ def _expect_valid(user_cfg): def _expect_error(user_cfg, substr: str): - """Validate and expect a ValueError mentioning substr.""" - with pytest.raises(ValueError) as ei: + """Validate and expect a ConfigError mentioning substr.""" + with pytest.raises(ConfigError) as ei: validate_config(user_cfg) msg = str(ei.value) assert substr in msg, f"Expected error message to contain '{substr}', got: {msg}"