diff --git a/deploy/auto-deploy.sh b/deploy/auto-deploy.sh index 30a5092..3ca4a4b 100755 --- a/deploy/auto-deploy.sh +++ b/deploy/auto-deploy.sh @@ -67,6 +67,18 @@ if ! git merge --ff-only "$DEPLOY_REMOTE/main" --quiet 2>&1; then exit 1 fi +# A running shell keeps the old script body after git updates this file. Re-exec +# once from the merged checkout so newly added sync/restart paths take effect in +# the same deploy instead of writing a false-success stamp. +if [ "${TELEO_AUTO_DEPLOY_REEXECED:-0}" != "1" ] \ + && [ "$OLD_SHA" != "none" ] \ + && git diff --name-only "$OLD_SHA" "$NEW_SHA" -- deploy/auto-deploy.sh deploy/leoclean-gateway-restart-required.sh \ + | grep -q .; then + log "Deploy logic changed; re-executing merged deploy script" + export TELEO_AUTO_DEPLOY_REEXECED=1 + exec bash "$DEPLOY_CHECKOUT/deploy/auto-deploy.sh" +fi + # Syntax check all Python files before copying ERRORS=0 for f in lib/*.py *.py diagnostics/*.py telegram/*.py tests/*.py hermes-agent/leoclean-plugins/vps/*/*.py; do diff --git a/docs/reports/leo-working-state-20260709/telegram-handler-m3taversal-oos-targeted-post-live-db-context-injection-20260713-score.json b/docs/reports/leo-working-state-20260709/telegram-handler-m3taversal-oos-targeted-post-live-db-context-injection-20260713-score.json new file mode 100644 index 0000000..1b18d41 --- /dev/null +++ b/docs/reports/leo-working-state-20260709/telegram-handler-m3taversal-oos-targeted-post-live-db-context-injection-20260713-score.json @@ -0,0 +1,128 @@ +{ + "database_context_all_ok": true, + "database_context_injection_count": 2, + "database_context_trace_ok": true, + "db_counts_changed": false, + "generated_at_utc": "2026-07-13T10:46:31.408830+00:00", + "mode": "working_leo_m3taversal_targeted_live_database_context_rescore", + "posted_to_telegram": false, + "production_db_apply_ran": false, + "safe_pass": false, + "score": { + "expected_prompt_count": 2, + "failures": [ + { + "behavioral_rule_schema_issues": [], + "broad_semantic_issues": [], + "concepts": { + "behavioral_rule_storage": false, + "heterogeneous_types": true, + "receipt": false, + "reviewed_policy_apply": false, + "staged_review_apply": true + }, + "current_schema_overclaims": [ + "unreviewed_claim_type" + ], + "custom_signals": {}, + "dimension": "database_composition_judgment", + "invalid_count_invariant_detected": false, + "legacy_pass": false, + "legacy_signals": { + "canonical_db": false, + "caveat_retention": false, + "next_action": true, + "no_overclaim": true, + "staging_or_review": false + }, + "max_response_words": 220, + "overclaim_detected": false, + "pass": false, + "prompt_id": "OOS-06", + "proposal_readiness_issues": [], + "response_issue_detected": false, + "response_too_long": false, + "source_evidence_semantic_issues": [], + "source_intake_issues": [], + "word_count": 207 + } + ], + "pass": false, + "passes": 1, + "scores": [ + { + "behavioral_rule_schema_issues": [], + "broad_semantic_issues": [], + "concepts": { + "bounded_intake_tier": true, + "real_source_identity": true, + "staged_review_apply": true, + "staging_without_apply_authorization": true + }, + "current_schema_overclaims": [], + "custom_signals": {}, + "dimension": "autonomous_source_intake_boundary", + "invalid_count_invariant_detected": false, + "legacy_pass": false, + "legacy_signals": { + "artifact": true, + "authorization": false, + "no_overclaim": true, + "staging_or_review": true + }, + "max_response_words": 220, + "overclaim_detected": false, + "pass": true, + "prompt_id": "OOS-14", + "proposal_readiness_issues": [], + "response_issue_detected": false, + "response_too_long": false, + "source_evidence_semantic_issues": [], + "source_intake_issues": [], + "word_count": 201 + }, + { + "behavioral_rule_schema_issues": [], + "broad_semantic_issues": [], + "concepts": { + "behavioral_rule_storage": false, + "heterogeneous_types": true, + "receipt": false, + "reviewed_policy_apply": false, + "staged_review_apply": true + }, + "current_schema_overclaims": [ + "unreviewed_claim_type" + ], + "custom_signals": {}, + "dimension": "database_composition_judgment", + "invalid_count_invariant_detected": false, + "legacy_pass": false, + "legacy_signals": { + "canonical_db": false, + "caveat_retention": false, + "next_action": true, + "no_overclaim": true, + "staging_or_review": false + }, + "max_response_words": 220, + "overclaim_detected": false, + "pass": false, + "prompt_id": "OOS-06", + "proposal_readiness_issues": [], + "response_issue_detected": false, + "response_too_long": false, + "source_evidence_semantic_issues": [], + "source_intake_issues": [], + "word_count": 207 + } + ] + }, + "selected_prompt_ids": [ + "OOS-14", + "OOS-06" + ], + "service_unchanged": true, + "source_results_json": "docs/reports/leo-working-state-20260709/telegram-handler-m3taversal-oos-targeted-post-live-db-context-injection-20260713.json", + "temp_profile_removed": true +} diff --git a/docs/reports/leo-working-state-20260709/telegram-handler-m3taversal-oos-targeted-post-live-db-context-injection-20260713.json b/docs/reports/leo-working-state-20260709/telegram-handler-m3taversal-oos-targeted-post-live-db-context-injection-20260713.json new file mode 100644 index 0000000..1abab8e --- /dev/null +++ b/docs/reports/leo-working-state-20260709/telegram-handler-m3taversal-oos-targeted-post-live-db-context-injection-20260713.json @@ -0,0 +1,154 @@ +{ + "before_service": { + "ActiveState": "active", + "ExecMainStartTimestamp": "Mon 2026-07-13 10:40:20 UTC", + "MainPID": "3520287", + "NRestarts": "0", + "SubState": "running" + }, + "changed_live_profile": false, + "database_context_all_ok": true, + "database_context_injection_count": 2, + "database_context_trace": [ + { + "contract_ids": [ + "reply_budget", + "source_intake" + ], + "contract_sha256": "9815d85fdd1dc8145d9db79207d7fe37b0df3c81e82b69075f009880d8bf6cd2", + "generated_at_utc": "2026-07-13T10:41:17.892279+00:00", + "injected": true, + "query_sha256": "45141620c3405af992012e2bd5000cabd8a6bffbc784082e856ea1a18b6aec06", + "source": "kb_tool.py --local context", + "status": "ok" + }, + { + "contract_ids": [ + "reply_budget", + "mixed_packet_composition" + ], + "contract_sha256": "f93791c5c0c04895d0bc3080a9eb869d175b61c48e7baa0df19e7ecbddbd7d0a", + "generated_at_utc": "2026-07-13T10:41:32.752160+00:00", + "injected": true, + "query_sha256": "13c255fda3a35cd913ba92960bcda1b4dbcf55a38f3301bdc2a74a8eb040df23", + "source": "kb_tool.py --local context", + "status": "ok" + } + ], + "db_counts_after": { + "kb_stage.kb_proposals": 26, + "public.claim_edges": 4916, + "public.claim_evidence": 4670, + "public.claims": 1837, + "public.sources": 4145 + }, + "db_counts_before": { + "kb_stage.kb_proposals": 26, + "public.claim_edges": 4916, + "public.claim_evidence": 4670, + "public.claims": 1837, + "public.sources": 4145 + }, + "db_counts_changed": false, + "generated_at_utc": "2026-07-13T10:41:14.394965+00:00", + "handler": { + "authorized": true, + "session_key": "agent:main:telegram:group:-5146042086:9070919", + "temp_profile": "/tmp/leo-direct-claim-handler-v9i4nyxp/profile" + }, + "harness_notes": [ + "Unchanged broad OOS-14 and OOS-06 prompts; current behavior is injected from live teleo-kb operational contracts.", + "The harness uses a temporary copy of the leoclean profile and does not post to Telegram.", + "This proves handler-level live VPS GatewayRunner behavior, not human-visible Telegram delivery." + ], + "mode": "live_vps_gatewayrunner_temp_profile_m3taversal_targeted_live_database_context", + "mutates_kb_by_harness": false, + "pass_runtime": true, + "posted_to_telegram": false, + "remote_report_path": "/tmp/leo-m3taversal-oos-targeted-live-db-context-a40b57083d5b.json", + "remote_returncode": 0, + "remote_run_id": "a40b57083d5b", + "results": [ + { + "claim_ceiling": "Live VPS GatewayRunner reply from temp leoclean profile; no Telegram post; no production DB apply authorized.", + "database_context_trace": [ + { + "contract_ids": [ + "reply_budget", + "source_intake" + ], + "contract_sha256": "9815d85fdd1dc8145d9db79207d7fe37b0df3c81e82b69075f009880d8bf6cd2", + "generated_at_utc": "2026-07-13T10:41:17.892279+00:00", + "injected": true, + "query_sha256": "45141620c3405af992012e2bd5000cabd8a6bffbc784082e856ea1a18b6aec06", + "source": "kb_tool.py --local context", + "status": "ok" + } + ], + "dimension": "autonomous_source_intake_boundary", + "ended_at_utc": "2026-07-13T10:41:32.657308+00:00", + "evidence_tier": "live_vps_gatewayrunner_temp_profile", + "mutates_kb": false, + "ok": true, + "prompt": "I hand Leo a document and say: absorb this as far as safely possible without making me approve every mechanical step. What can Leo capture and stage immediately, what real source identity must be retained, and where does explicit approval begin? Explain only; do not ingest this prompt.", + "prompt_id": "OOS-14", + "reply": "What Leo captures immediately, no approval gate:\n\n- Artifact content, a stable storage_path or URL, and a content hash \u2014 those three are the real source identity per the schema (public.sources columns). Chat label, filename, or attachment ref are not source identity and must not be treated as such.\n- Candidate claims extracted into a pending_review proposal packet \u2014 staged, not applied.\n\nWhere approval begins:\n\nAny write to a guarded canonical public.* table (applying a proposal, committing a source row, landing a KB claim) requires explicit authorization. Staging and extraction run free; canonical apply does not.\n\nOne constraint to name plainly:\n\nThe local compiler is proven; live VPS/chat intake is not shipped. Leo cannot invoke the compiler from this chat session. What I can do right now is reason over a document you paste and produce a structured proposal packet in the reply \u2014 you then route that output through the compiler manually. That gap is the honest ceiling on \"absorb without approval.\"\n\nSo the practical boundary is: paste the document, I extract and structure candidates, you get a reviewable packet, you authorize the apply run. Everything upstream of that apply is mechanical and safe; everything at or past it is yours to gate.", + "started_at_utc": "2026-07-13T10:41:15.113487+00:00", + "turn": 1 + }, + { + "claim_ceiling": "Live VPS GatewayRunner reply from temp leoclean profile; no Telegram post; no production DB apply authorized.", + "database_context_trace": [ + { + "contract_ids": [ + "reply_budget", + "mixed_packet_composition" + ], + "contract_sha256": "f93791c5c0c04895d0bc3080a9eb869d175b61c48e7baa0df19e7ecbddbd7d0a", + "generated_at_utc": "2026-07-13T10:41:32.752160+00:00", + "injected": true, + "query_sha256": "13c255fda3a35cd913ba92960bcda1b4dbcf55a38f3301bdc2a74a8eb040df23", + "source": "kb_tool.py --local context", + "status": "ok" + } + ], + "dimension": "database_composition_judgment", + "ended_at_utc": "2026-07-13T10:41:49.261157+00:00", + "evidence_tier": "live_vps_gatewayrunner_temp_profile", + "mutates_kb": false, + "ok": true, + "prompt": "Suppose a new research packet contains factual observations, a strategic framework, a disputed interpretation, a governance rule, and a correction to an old belief. How should Leo compose the database so it can reason over the packet later without flattening it all into claims? Give the review and apply sequence too, but do not write anything now.", + "prompt_id": "OOS-06", + "reply": "Five elements, five destinations \u2014 nothing flattened:\n\n- Factual observations \u2192 claims rows (type: observation, status: review) backed by a sources row and evidence rows linking evidence to claim. Disputed interpretation \u2192 claims row (status: disputed) plus a claim_edges row pointing from the dispute to the observation it contests \u2014 edges connect claim IDs only, never beliefs or reasoning_tools.\n- Strategic framework \u2192 reasoning_tools row (name, description, category). No scope field exists; scope goes in description.\n- Governance rule and belief correction \u2192 staged but not applied. Both behavioral_rules and beliefs are guards-protected; keep in proposal packet flagged keep_staged until a separate apply capability is authorized.\n\nReview and apply sequence:\n\n1. Reviewer inspects the full packet \u2014 five distinct sections, each mapped to its table, schema guards checked (no falsifier on claims, no status on behavioral_rules, edge endpoints are claim IDs).\n2. On approval: apply in dependency order \u2014 sources first, then claims, then evidence, then claim_edges, then reasoning_tools. That order ensures foreign keys resolve.\n3. Governance rule and belief correction surface as a second review gate, separate PR. Reviewer confirms the belief row to update (by ID) and the exact old text before any write lands.\n\nNothing in the packet shares a row type. Later reasoning can query each layer independently without untangling a flat claim pile.", + "started_at_utc": "2026-07-13T10:41:32.660716+00:00", + "turn": 2 + } + ], + "service_before_after": { + "after": { + "ActiveState": "active", + "ExecMainStartTimestamp": "Mon 2026-07-13 10:40:20 UTC", + "MainPID": "3520287", + "NRestarts": "0", + "SubState": "running" + }, + "before": { + "ActiveState": "active", + "ExecMainStartTimestamp": "Mon 2026-07-13 10:40:20 UTC", + "MainPID": "3520287", + "NRestarts": "0", + "SubState": "running" + }, + "unchanged_from_preexisting_live_readback": true + }, + "source": { + "chat_id": "-5146042086", + "chat_name": "Leo", + "chat_type": "group", + "platform": "telegram", + "user_id": "9070919", + "user_name": "codex handler direct claim" + }, + "source_report_path": "/Users/user/Documents/Codex/2026-07-09/019f34eb-d297-72d0-b7e2-b222d5515ab9-load/work/teleo-infra-main/docs/reports/leo-working-state-20260709/telegram-handler-m3taversal-oos-targeted-post-live-db-context-injection-20260713.json", + "temp_profile_removed": true +} diff --git a/docs/reports/leo-working-state-20260709/telegram-handler-m3taversal-oos-targeted-post-live-db-context-injection-20260713.md b/docs/reports/leo-working-state-20260709/telegram-handler-m3taversal-oos-targeted-post-live-db-context-injection-20260713.md new file mode 100644 index 0000000..98440e7 --- /dev/null +++ b/docs/reports/leo-working-state-20260709/telegram-handler-m3taversal-oos-targeted-post-live-db-context-injection-20260713.md @@ -0,0 +1,20 @@ +# Working Leo Targeted Live Database Context + +Generated UTC: `2026-07-13T10:46:31.408830+00:00` +Safe pass: `False` +Prompts: `1/2` +Database context injections: `2` +Database context all OK: `True` +DB counts changed: `False` +Service unchanged: `True` +Temporary profile removed: `True` +Posted to Telegram: `False` + +## Prompt Scores + +- `OOS-14`: `pass=True`, words=`201` +- `OOS-06`: `pass=False`, words=`207` + +## Claim Ceiling + +This proves the live VPS GatewayRunner received query-specific current database contracts before reasoning. OOS-14 passed; OOS-06 still invented unsupported claim values and omitted the apply receipt. No Telegram post or production DB apply ran. diff --git a/hermes-agent/leoclean-bin/kb_tool.py b/hermes-agent/leoclean-bin/kb_tool.py index 3f72bbc..53790f3 100755 --- a/hermes-agent/leoclean-bin/kb_tool.py +++ b/hermes-agent/leoclean-bin/kb_tool.py @@ -330,12 +330,16 @@ def with_proposal_readiness(proposal: dict[str, Any]) -> dict[str, Any]: def operational_contracts( - query: str, *, current_schema: dict[str, list[str] | tuple[str, ...]] | None = None + query: str, + *, + current_schema: dict[str, list[str] | tuple[str, ...]] | None = None, + current_constraints: dict[str, dict[str, str]] | None = None, ) -> list[dict[str, Any]]: """Return question-specific current-runtime truth, not recalled architecture.""" lowered = query.lower() schema = current_schema if current_schema is not None else CURRENT_PUBLIC_SCHEMA + constraints = current_constraints or {} contracts: list[dict[str, Any]] = [ { "id": "reply_budget", @@ -399,6 +403,8 @@ def operational_contracts( "claims has no falsifier field", "behavioral_rules has no status field", "applied_at belongs to the proposal receipt, not each row", + "use only claim type/status values allowed by the current Postgres CHECK constraints", + "pending_review is a proposal state, not a public.claims status", ], "current_columns": { name: list(schema.get(name, ())) @@ -411,6 +417,16 @@ def operational_contracts( "beliefs", ) }, + "current_constraints": { + name: constraints.get(name, {}) + for name in ("claims", "claim_edges", "beliefs") + }, + "review_apply_sequence": [ + "stage one typed pending_review proposal packet without canonical writes", + "review mappings, evidence, unsupported changes, and exact row values", + "apply only supported collections after explicit authorization and keep unsupported changes staged", + "record proposal-level applied_at plus created/updated row IDs and postflight readback", + ], } ) @@ -475,6 +491,25 @@ def load_current_public_schema(args: argparse.Namespace, table_names: set[str]) return {row["table_name"]: row["columns"] for row in psql_json(args, sql)} +def load_current_public_constraints(args: argparse.Namespace, table_names: set[str]) -> dict[str, dict[str, str]]: + if not table_names: + return {} + sql = f""" + select jsonb_build_object( + 'table_name', t.relname, + 'constraints', jsonb_object_agg(c.conname, pg_get_constraintdef(c.oid) order by c.conname) + )::text + from pg_constraint c + join pg_class t on t.oid = c.conrelid + join pg_namespace n on n.oid = t.relnamespace + where n.nspname = 'public' + and t.relname = any({sql_array(sorted(table_names))}) + group by t.relname + order by t.relname; + """ + return {row["table_name"]: row["constraints"] for row in psql_json(args, sql)} + + def context_operational_contracts(args: argparse.Namespace, query: str) -> list[dict[str, Any]]: initial = operational_contracts(query) contract_ids = {contract["id"] for contract in initial} @@ -487,7 +522,11 @@ def context_operational_contracts(args: argparse.Namespace, query: str) -> list[ ) if not table_names: return initial - return operational_contracts(query, current_schema=load_current_public_schema(args, table_names)) + return operational_contracts( + query, + current_schema=load_current_public_schema(args, table_names), + current_constraints=load_current_public_constraints(args, table_names), + ) def claim_base_url() -> str: diff --git a/scripts/working_leo_m3taversal_oos_benchmark.py b/scripts/working_leo_m3taversal_oos_benchmark.py index da99a71..a85ff0c 100755 --- a/scripts/working_leo_m3taversal_oos_benchmark.py +++ b/scripts/working_leo_m3taversal_oos_benchmark.py @@ -423,7 +423,11 @@ CONCEPT_PATTERNS: dict[str, tuple[re.Pattern[str], ...]] = { ), "bounded_intake_tier": ( re.compile(r"build-only|local(?:ly)?|clone", re.I), - re.compile(r"not yet.{0,80}(?:live|autonomous|production)|not.{0,80}live-VPS", re.I | re.S), + re.compile( + r"not yet.{0,80}(?:live|autonomous|production)|not.{0,80}live-VPS|" + r"live.{0,80}(?:not shipped|not live|unavailable)", + re.I | re.S, + ), ), "valid_supersession": ( re.compile(r"new|replacement", re.I), @@ -671,8 +675,13 @@ def broad_semantic_issues(reply: str) -> list[str]: findings.add("belief_row_given_claim_edge") if ROW_LEVEL_APPLIED_AT_RE.search(reply): findings.add("applied_at_assigned_to_each_apply_step") - if REASONING_TOOL_CLAIM_EDGE_RE.search(reply): + for segment in re.split(r"(?<=[.!?])\s+|\n+", reply): + if not REASONING_TOOL_CLAIM_EDGE_RE.search(segment): + continue + if re.search(r"(?:never|do not|don't|must not|cannot).{0,100}reasoning_tools?", segment, re.I | re.S): + continue findings.add("claim_edge_pointed_at_reasoning_tool") + break return sorted(findings) diff --git a/tests/test_hermes_leoclean_kb_bridge_source.py b/tests/test_hermes_leoclean_kb_bridge_source.py index 852581b..13dfeb9 100644 --- a/tests/test_hermes_leoclean_kb_bridge_source.py +++ b/tests/test_hermes_leoclean_kb_bridge_source.py @@ -253,7 +253,11 @@ def test_vps_bridge_context_contract_reads_current_schema_from_postgres(monkeypa def fake_psql_json(_args, sql): captured_sql.append(sql) - return [{"table_name": "sources", "columns": ["id", "source_type", "url", "hash", "new_live_column"]}] + if "information_schema.columns" in sql: + return [ + {"table_name": "sources", "columns": ["id", "source_type", "url", "hash", "new_live_column"]} + ] + return [{"table_name": "sources", "constraints": {"sources_pkey": "PRIMARY KEY (id)"}}] monkeypatch.setattr(module, "psql_json", fake_psql_json) contracts = { @@ -270,9 +274,41 @@ def test_vps_bridge_context_contract_reads_current_schema_from_postgres(monkeypa "hash", "new_live_column", ] - assert len(captured_sql) == 1 + assert len(captured_sql) == 2 assert "information_schema.columns" in captured_sql[0] assert "table_name = any" in captured_sql[0] + assert "pg_constraint" in captured_sql[1] + + +def test_vps_bridge_mixed_contract_reads_live_postgres_constraints(monkeypatch) -> None: + module = _load_module(BRIDGE_DIR / "kb_tool.py") + + def fake_psql_json(_args, sql): + if "information_schema.columns" in sql: + return [{"table_name": "claims", "columns": ["id", "type", "text", "status"]}] + return [ + { + "table_name": "claims", + "constraints": { + "claims_type_check": "CHECK ((type = ANY (ARRAY['empirical', 'structural'])))", + "claims_status_check": "CHECK ((status = ANY (ARRAY['open', 'retired'])))", + }, + } + ] + + monkeypatch.setattr(module, "psql_json", fake_psql_json) + + contracts = { + item["id"]: item + for item in module.context_operational_contracts( + SimpleNamespace(), + "Compose a packet with factual observations, a strategic framework, and a governance rule.", + ) + } + mixed = contracts["mixed_packet_composition"] + assert "'empirical'" in mixed["current_constraints"]["claims"]["claims_type_check"] + assert "'open'" in mixed["current_constraints"]["claims"]["claims_status_check"] + assert "proposal-level applied_at" in mixed["review_apply_sequence"][-1] def test_vps_bridge_decision_matrix_status_checks_schema_tables(monkeypatch) -> None: diff --git a/tests/test_teleo_agent_systemd.py b/tests/test_teleo_agent_systemd.py index 2bfc1ea..3fd7a00 100644 --- a/tests/test_teleo_agent_systemd.py +++ b/tests/test_teleo_agent_systemd.py @@ -105,6 +105,8 @@ def test_deploy_scripts_sync_leoclean_skills_and_restart_gateway_for_runtime_cha assert 'hermes-agent/leoclean-skills/vps/' in auto_deploy assert 'hermes-agent/leoclean-plugins/vps/' in auto_deploy assert 'deploy/leoclean-gateway-restart-required.sh' in auto_deploy + assert 'TELEO_AUTO_DEPLOY_REEXECED' in auto_deploy + assert 'exec bash "$DEPLOY_CHECKOUT/deploy/auto-deploy.sh"' in auto_deploy assert 'add_restart_if_unit_active leoclean-gateway' in auto_deploy assert 'sudo systemctl restart "$svc"' in auto_deploy assert "sudo systemctl restart $RESTART" not in auto_deploy diff --git a/tests/test_working_leo_m3taversal_oos_benchmark.py b/tests/test_working_leo_m3taversal_oos_benchmark.py index 2fafead..cf4110b 100644 --- a/tests/test_working_leo_m3taversal_oos_benchmark.py +++ b/tests/test_working_leo_m3taversal_oos_benchmark.py @@ -441,6 +441,17 @@ def test_oos_composition_rejects_claim_edge_to_reasoning_tool() -> None: assert benchmark.broad_semantic_issues(reply) == ["claim_edge_pointed_at_reasoning_tool"] +def test_oos_composition_accepts_explicit_reasoning_tool_edge_prohibition() -> None: + reply = "claim_edges connect claim IDs only; they must never point at beliefs or reasoning_tools." + assert benchmark.broad_semantic_issues(reply) == [] + + +def test_oos_source_intake_accepts_live_not_shipped_wording() -> None: + patterns = benchmark.CONCEPT_PATTERNS["bounded_intake_tier"] + reply = "The local compiler is proven; live VPS/chat intake is not shipped." + assert all(pattern.search(reply) for pattern in patterns) + + def test_oos_runtime_case_accepts_explicit_persistence_tiers() -> None: token = "demo-ledger-deadbeef" prompt = benchmark.prompt_catalog(token)[9]