CPL-3: architecture facts over the pinned carriers + query_code op=architecture

Five pure queries with Ouroboros self-evolution as consumer #1:
owner_of(path|symbol), domain_dependencies(d), facade_consumers(sym),
persistence_entities_written_by(sym), protected_contracts_affected(diff) —
all over data the repo already pins (ouroboros/domains.toml, the generated
facade/frozen-contract inventories, docs/PERSISTENCE.md, the
runtime_mode_policy protected inventories). New D05 leaf
ouroboros/code_intelligence_architecture.py beside code_intelligence
(manifest row added; DOMAIN_MAP regenerated); the model consumes through
the EXISTING query_code tool via op=architecture — no new registry tool.
Suite: tests/test_architecture_facts.py pins every query on real examples
plus completeness against each carrier in both directions.
This commit is contained in:
Ouroboros 2026-09-01 11:33:22 +00:00 • committed by Anton Razzhigaev
parent 46cf04673d
commit 1a4e763808
7 changed files with 1001 additions and 28 deletions

View file

@ -72,7 +72,7 @@ Schema (fixed; one row per artifact-level delta family, never per commit):
| ABI-10 | plan-item | Reviewer comma-lists → actor rows: the model is already upstream (#384); residual migration read REMOVED by the F3.1 lane D4 train (owner 5.4=A) — legacy block deleted, shipped default panel over the derived env plane (identical models on every config class), comma keys + phase-5 route envs retired via RETIRED_SETTING_KEYS, derived projection kept, bench templates migrated to structured slots with the same models | superseded-by-upstream | done | F3 | tests/test_comma_list_remnant_sweep.py (F3.3 count-anchored remnant sweep — the phase CI gate) + tests/test_comma_list_sweep.py (F3.1 migration-read sweep) + tests/test_reviewer_slot_config.py |
| CPL-1 | plan-item | Production manifest ouroboros/domains.toml (module→domain 1:1 over all 488 tracked runtime modules; completeness = red on drift) + domain gate: strict direction matrix pinned as FACTUAL baseline data (164 pairs; new direction = red), cycle gate against the pinned SCC ceiling (today's single 20-domain strict-quotient SCC grandfathered per the Ф5 baseline discipline — current reality = baseline, target `cycle_groups = []`; growth = red), lazy/dynamic import classification pinned (92 lazy-only pairs), cross-domain literal-copy ban (baseline EMPTY — every new copied body is red); DOMAIN_MAP.md generated from the manifest (gen/verify pair). Report-only Ф0 stage superseded: the manifest moved from scripts/v7next_domains.toml, the report stays the witness-level companion on the shared scripts/domain_graph.py core | retain | done | F5 | scripts/check_domains.py (gate + --write regenerator) + tests/test_domain_manifest.py (verify half incl. synthetic red-branch pins) + docs/DOMAIN_MAP.md |
| CPL-2 | plan-item | gen/verify pairs shipped for all three inventories: frozen-contracts table (ARCHITECTURE §11.1 machine extraction, owner/anchor path resolution, contracts-package coverage gap pinned), data-layout tree (ARCHITECTURE §1 Data-layout tree — the factual carrier here; the reference PERSISTENCE_OWNERS.md doc does not exist in this tree — probed entry-by-entry against tracked paths and runtime-source literals), facade inventory (AST noqa:F401 re-export scan over the manifest population); staleness = red CI | retain | done | F5 | scripts/regenerate_inventories.py (--check) + tests/test_generated_inventories.py + docs/v7next/FROZEN_CONTRACTS_INVENTORY.md + docs/v7next/DATA_LAYOUT_INVENTORY.md + docs/v7next/FACADE_INVENTORY.md |
| CPL-3 | plan-item | code_intelligence architecture facts: owner_of(path\|symbol), domain_dependencies(d), facade_consumers(sym), persistence_entities_written_by(sym), protected_contracts_affected(diff); consumer №1 = Ouroboros self-evolution (Q12=B: completeness ships whole in v7.0) | retain | pending | F5 | code_intelligence fact suite (new) |
| CPL-3 | plan-item | code_intelligence architecture facts: owner_of(path\|symbol), domain_dependencies(d), facade_consumers(sym), persistence_entities_written_by(sym), protected_contracts_affected(diff); consumer №1 = Ouroboros self-evolution (Q12=B: completeness ships whole in v7.0). LANDED by the F5 lane C: five pure queries over the pinned carriers (domain manifest, facade/persistence/frozen-contract inventories, runtime_mode_policy protected sets) in `ouroboros/code_intelligence_architecture.py` — a D05 leaf beside code_intelligence; model seam = the EXISTING query_code tool, new `op=architecture` (no new registry tool — lane decision, ledger F5 lane C section) | retain | done | F5 | tests/test_architecture_facts.py (real-example pins + carrier completeness both ways + the query_code op seam) |
| CPL-4 | plan-item | Persistence: schema_version/migration/retention/reset decision per durable entity, local (no generic framework); close §16 findings (undocumented planes, unbounded ledgers, mismatched temp — the §16 source is unrecoverable; inventory rebuilt by factual writer scan, disclosed in the F5 lane B ledger section with the candidate-fix table) | retain | done | F5 | docs/PERSISTENCE.md + tests/test_persistence_inventory.py (AST writer scan, count-anchored both ways) |
| CPL-5 | plan-item | Runtime invariant model-visible⟺logged, narrowed per F15: sealed model_send records at the last host-controlled pre-transport seam, typed exclusions (provider-native queries/transforms/secrets), reverse-⟺ for model_send only; canonicalization design note BEFORE code (batch-1 Q8=A confirmed) | retain | in-progress | F5 | design note docs/v7next/DESIGN_MODEL_VISIBLE_LOGGED.md (landed, F5 lane B) → invariant reconstruction suite (next lane) |
| CPL-6 | plan-item | Conformance contracts for multi-provider seams: LLM providers and the executor axis native\|harness — one normative shared suite every new provider must pass | retain | pending | F5 | shared conformance suite (new) |

File diff suppressed because one or more lines are too long

View file

@ -11,24 +11,24 @@ The manifest is the SSOT of the module→domain assignment (1:1, complete over t
| D01 | Agent core & main loop | 29 | 2 |
| D02 | LLM client, routing & providers | 33 | 13 |
| D03 | Context assembly, fit & compaction | 11 | 1 |
| D04 | Tool execution: registry, access & typed results | 20 | 0 |
| D04 | Tool execution: registry, access & typed results | 19 | 0 |
| D05 | Tool surfaces: files, code, shell, media, external | 24 | 1 |
| D06 | Review stack | 62 | 17 |
| D07 | Delegation, subagents & Claudexor | 47 | 17 |
| D08 | Supervisor: queue, workers, events & runtime control | 41 | 4 |
| D09 | Cancellation, owner control & process custody | 12 | 0 |
| D07 | Delegation, subagents & Claudexor | 45 | 17 |
| D08 | Supervisor: queue, workers, events & runtime control | 40 | 4 |
| D09 | Cancellation, owner control & process custody | 11 | 0 |
| D10 | Git, update & release machinery | 28 | 1 |
| D11 | Gateway, server & Web UI | 45 | 2 |
| D11 | Gateway, server & Web UI | 42 | 2 |
| D12 | Settings & configuration | 14 | 1 |
| D13 | Safety, guards & runtime mode | 8 | 2 |
| D14 | Skills & extensions | 51 | 8 |
| D15 | Memory, knowledge, consciousness & self-evolution | 16 | 1 |
| D16 | Observability, usage accounting & cost | 8 | 0 |
| D17 | Projects, workspaces & task results | 20 | 1 |
| D18 | Launcher, packaging, platform & shared substrate | 11 | 0 |
| D17 | Projects, workspaces & task results | 19 | 1 |
| D18 | Launcher, packaging, platform & shared substrate | 10 | 0 |
| D19 | Frozen contracts (ABI) | 10 | 0 |
| D20 | Presence | 9 | 9 |
| **total** | | **499** | **80** |
| **total** | | **489** | **80** |
## Dependency direction matrix (strict, pinned)
@ -65,7 +65,7 @@ Rows may import columns (`[graph].allowed`). `·` = forbidden direction.
## Hidden coupling (classified out of the strict graph)
- lazy-only cross-domain pairs: **93**
- lazy-only cross-domain pairs: **92**
- D01->D08
- D01->D10
- D01->D11
@ -127,7 +127,6 @@ Rows may import columns (`[graph].allowed`). `·` = forbidden direction.
- D13->D15
- D14->D08
- D14->D13
- D14->D17
- D14->D20
- D15->D06
- D15->D07
@ -263,7 +262,6 @@ No function body (≥ 10 normalized lines) is shared verbatim across domains. Ne
- `ouroboros/tool_policy.py`
- `ouroboros/tools/__init__.py`
- `ouroboros/tools/extension_dispatch.py`
- `ouroboros/tools/process_facts.py`
- `ouroboros/tools/registry.py`
- `ouroboros/tools/registry_core.py`
- `ouroboros/tools/registry_guard_process.py`
@ -278,9 +276,10 @@ No function body (≥ 10 normalized lines) is shared verbatim across domains. Ne
- `ouroboros/artifacts.py`
- `ouroboros/code_intelligence.py`
- `ouroboros/code_intelligence_architecture.py`
- `ouroboros/code_search_rg.py`
- `ouroboros/mcp_client.py`
- `ouroboros/process_interpreters.py`
- `ouroboros/python_interpreter.py`
- `ouroboros/tools/browser.py`
- `ouroboros/tools/core.py`
- `ouroboros/tools/core_artifacts.py`
@ -288,7 +287,6 @@ No function body (≥ 10 normalized lines) is shared verbatim across domains. Ne
- `ouroboros/tools/edit_ops.py`
- `ouroboros/tools/health.py`
- `ouroboros/tools/media.py`
- `ouroboros/tools/owner_delivery.py`
- `ouroboros/tools/query_code.py`
- `ouroboros/tools/recent_tasks.py`
- `ouroboros/tools/search.py`
@ -382,7 +380,6 @@ No function body (≥ 10 normalized lines) is shared verbatim across domains. Ne
- `ouroboros/delegate_pending.py` *
- `ouroboros/delegate_progress.py`
- `ouroboros/delegate_recovery.py` *
- `ouroboros/delegate_registration_policy.py`
- `ouroboros/delegate_shared.py`
- `ouroboros/delegate_source_coverage.py` *
- `ouroboros/delegate_start_claims.py` *
@ -411,7 +408,6 @@ No function body (≥ 10 normalized lines) is shared verbatim across domains. Ne
- `ouroboros/tools/delegate_payload_patch.py`
- `ouroboros/tools/delegate_terminal_evidence.py`
- `ouroboros/tools/join_ledger.py`
- `ouroboros/tools/patch_verdict.py`
- `ouroboros/tools/subagent_integration.py`
- `ouroboros/tools/subagent_integration_delegated.py`
- `ouroboros/tools/task_tree.py`
@ -451,7 +447,6 @@ No function body (≥ 10 normalized lines) is shared verbatim across domains. Ne
- `supervisor/subagent_task_truth.py` *
- `supervisor/task_admission.py`
- `supervisor/task_dispatch.py` *
- `supervisor/telemetry_events.py`
- `supervisor/worker_assignment.py`
- `supervisor/worker_chat_lane.py`
- `supervisor/worker_health.py`
@ -464,7 +459,6 @@ No function body (≥ 10 normalized lines) is shared verbatim across domains. Ne
- `ouroboros/cancel_intents.py`
- `ouroboros/owner_hurry.py`
- `ouroboros/owner_quiz.py`
- `ouroboros/process_containment.py`
- `ouroboros/process_custody.py`
- `ouroboros/server_control.py`
@ -514,7 +508,6 @@ No function body (≥ 10 normalized lines) is shared verbatim across domains. Ne
- `ouroboros/gateway/claudexor_accounts.py`
- `ouroboros/gateway/contracts.py`
- `ouroboros/gateway/control.py`
- `ouroboros/gateway/endpoint_index.py`
- `ouroboros/gateway/extensions.py`
- `ouroboros/gateway/files.py`
- `ouroboros/gateway/history.py`
@ -529,13 +522,11 @@ No function body (≥ 10 normalized lines) is shared verbatim across domains. Ne
- `ouroboros/gateway/presence_settings.py` *
- `ouroboros/gateway/projects.py`
- `ouroboros/gateway/router.py`
- `ouroboros/gateway/routing_decision.py`
- `ouroboros/gateway/schedules.py`
- `ouroboros/gateway/schema.py`
- `ouroboros/gateway/settings.py`
- `ouroboros/gateway/skill_publish.py` *
- `ouroboros/gateway/state.py`
- `ouroboros/gateway/task_decision.py`
- `ouroboros/gateway/task_events.py`
- `ouroboros/gateway/task_hurry.py`
- `ouroboros/gateway/task_list_scan.py`
@ -678,7 +669,6 @@ No function body (≥ 10 normalized lines) is shared verbatim across domains. Ne
- `ouroboros/project_sources.py`
- `ouroboros/projects_registry.py`
- `ouroboros/retention.py`
- `ouroboros/routing_wait.py`
- `ouroboros/task_result_schema.py`
- `ouroboros/task_results.py`
- `ouroboros/task_status.py`
@ -697,7 +687,6 @@ No function body (≥ 10 normalized lines) is shared verbatim across domains. Ne
- `ouroboros/launcher_bootstrap.py`
- `ouroboros/launcher_server_reaper.py`
- `ouroboros/launcher_windows_runtime.py`
- `ouroboros/node_runtime.py`
- `ouroboros/packaged_cli.py`
- `ouroboros/packaged_cli_install.py`
- `ouroboros/platform_layer.py`

View file

@ -0,0 +1,628 @@
"""Architecture facts over the pinned domain/contract/persistence carriers (CPL-3).
Five pure queries whose consumer #1 is Ouroboros itself (self-evolution):
- ``owner_of(path|symbol)`` — the domain owner per ``ouroboros/domains.toml``;
- ``domain_dependencies(d)`` — the manifest-baseline direction edges of one domain;
- ``facade_consumers(sym)`` — who imports through a compatibility facade (the
``noqa: F401`` re-export convention the facade inventory documents);
- ``persistence_entities_written_by(sym)`` — the durable entities a writer owns
per ``docs/PERSISTENCE.md``;
- ``protected_contracts_affected(diff)`` — the protected surfaces
(``runtime_mode_policy`` inventories) and frozen-contract rows
(``docs/v7next/FROZEN_CONTRACTS_INVENTORY.md``) a change set touches.
Everything here is a pure function over data the repository already pins as
SSOT — the domain manifest, the generated inventories, and the protected-path
inventories. No LLM, no caches, no ledgers: every reader takes an explicit
``repo_root``, reads the carrier files fresh, and raises a teaching
``ValueError`` when a carrier is missing or an argument is malformed. The
model consumes these through the existing ``query_code`` tool
(``op=architecture``) — the seam decision is recorded in the campaign ledger
(``docs/v7next/LEDGER_CORRECTIONS.md``, F5 lane C section).
"""
from __future__ import annotations
import ast
import pathlib
import re
from dataclasses import dataclass
from typing import Any, Dict, Iterable, List, Tuple
from ouroboros.code_intelligence import CodeInventory, _resolve_relative_import
DOMAIN_MANIFEST_RELPATH = "ouroboros/domains.toml"
PERSISTENCE_DOC_RELPATH = "docs/PERSISTENCE.md"
FROZEN_INVENTORY_RELPATH = "docs/v7next/FROZEN_CONTRACTS_INVENTORY.md"
ARCHITECTURE_FACTS = (
"owner_of",
"domain_dependencies",
"facade_consumers",
"persistence_entities_written_by",
"protected_contracts_affected",
)
# The facade convention the generated facade inventory documents: a top-level
# ``from <module> import name`` carrying the F401-noqa marker is a re-export.
_NOQA_F401 = re.compile(r"#\s*noqa(?::[^#]*\bF401\b|\s*$|:\s*$)")
_BACKTICK_SPAN = re.compile(r"`([^`]+)`")
_WORD = re.compile(r"[A-Za-z_][A-Za-z0-9_]*")
_DIFF_GIT = re.compile(r"^diff --git a/(\S+) b/(\S+)$", re.MULTILINE)
_DIFF_FILE = re.compile(r"^(?:\+\+\+|---) (?:[ab]/)?(\S+)", re.MULTILINE)
_DIFF_RENAME = re.compile(r"^rename (?:from|to) (\S+)$", re.MULTILINE)
# ---------------------------------------------------------------------------
# Carrier loading
# ---------------------------------------------------------------------------
@dataclass(frozen=True)
class DomainManifest:
"""The parsed domain manifest: vocabulary, assignment, baseline edges."""
domains: Dict[str, str] # "D02" -> title
modules: Dict[str, str] # repo-relative path -> domain id
graph_allowed: Tuple[str, ...] # strict baseline "D01->D02" pairs
lazy_only: Tuple[str, ...] # lazy-only baseline pairs
def _read_carrier(repo_root: pathlib.Path, relpath: str) -> str:
path = pathlib.Path(repo_root) / relpath
try:
return path.read_text(encoding="utf-8")
except OSError as exc:
raise ValueError(
f"architecture carrier {relpath} is unreadable under {repo_root} "
f"({exc}); architecture facts are defined over an Ouroboros "
"checkout that carries its pinned inventories"
) from exc
def load_domain_manifest(repo_root: pathlib.Path) -> DomainManifest:
try: # Python 3.11+
import tomllib
except ImportError: # pragma: no cover - 3.10 venvs ship tomli
import tomli as tomllib
raw = _read_carrier(repo_root, DOMAIN_MANIFEST_RELPATH)
try:
data = tomllib.loads(raw)
except Exception as exc:
raise ValueError(f"{DOMAIN_MANIFEST_RELPATH} does not parse as TOML: {exc}") from exc
graph = data.get("graph") or {}
return DomainManifest(
domains={str(k): str(v) for k, v in (data.get("domains") or {}).items()},
modules={str(k): str(v) for k, v in (data.get("modules") or {}).items()},
graph_allowed=tuple(str(p) for p in graph.get("allowed") or ()),
lazy_only=tuple(str(p) for p in graph.get("lazy_only") or ()),
)
def _module_dotted(path: str) -> str:
parts = path[:-3].split("/") if path.endswith(".py") else path.split("/")
if parts and parts[-1] == "__init__":
parts = parts[:-1]
return ".".join(parts)
def _split_md_row(line: str) -> List[str]:
"""Split one markdown table row on unescaped pipes (inventory convention)."""
body = line.strip().strip("|")
cells: List[str] = []
cur: List[str] = []
escaped = False
for ch in body:
if escaped:
cur.append(ch)
escaped = False
elif ch == "\\":
cur.append(ch)
escaped = True
elif ch == "|":
cells.append("".join(cur).strip())
cur = []
else:
cur.append(ch)
cells.append("".join(cur).strip())
return cells
# ---------------------------------------------------------------------------
# owner_of
# ---------------------------------------------------------------------------
@dataclass(frozen=True)
class DomainOwner:
query: str
module: str # the manifest module the query resolved to
domain: str # "D02"
domain_title: str
via: str # module_path | dotted_module | symbol_definition
def owner_of(
repo_root: pathlib.Path,
query: str,
*,
inventory: CodeInventory | None = None,
) -> Tuple[DomainOwner, ...]:
"""Domain owner(s) of a module path, dotted module, or defined symbol.
A path or dotted module resolves directly against the manifest; a bare
symbol resolves through the code inventory to its defining module(s) and
each definition inside the manifest population reports its owner. A target
outside the runtime module population returns ``()`` — no domain owns it.
"""
manifest = load_domain_manifest(repo_root)
text = str(query or "").strip().replace("\\", "/")
if not text:
raise ValueError("owner_of requires a module path, dotted module, or symbol name")
norm = text[2:] if text.startswith("./") else text
def _owner(path: str, via: str) -> DomainOwner:
domain = manifest.modules[path]
return DomainOwner(text, path, domain, manifest.domains.get(domain, ""), via)
if norm in manifest.modules:
return (_owner(norm, "module_path"),)
if "/" in norm or norm.endswith(".py"):
return () # a real path outside the runtime module population
dotted_map = {_module_dotted(path): path for path in manifest.modules}
if norm in dotted_map:
return (_owner(dotted_map[norm], "dotted_module"),)
if "." in norm:
return () # dotted, but not a population module
if inventory is None:
from ouroboros.code_intelligence import build_code_inventory
inventory = build_code_inventory(pathlib.Path(repo_root), persist=False)
from ouroboros.code_intelligence import symbol_definitions
owners = {
file.path
for file, _symbol in symbol_definitions(inventory, norm)
if file.path in manifest.modules
}
return tuple(_owner(path, "symbol_definition") for path in sorted(owners))
# ---------------------------------------------------------------------------
# domain_dependencies
# ---------------------------------------------------------------------------
@dataclass(frozen=True)
class DomainDependencies:
domain: str
title: str
outgoing: Tuple[str, ...] # strict manifest-baseline edges out of the domain
incoming: Tuple[str, ...] # strict edges into the domain
lazy_outgoing: Tuple[str, ...] # lazy-only baseline edges out
lazy_incoming: Tuple[str, ...] # lazy-only baseline edges in
def domain_dependencies(repo_root: pathlib.Path, domain: str) -> DomainDependencies:
"""The manifest-baseline dependency edges of one domain (both directions)."""
manifest = load_domain_manifest(repo_root)
dom = str(domain or "").strip().upper()
if dom not in manifest.domains:
known = ", ".join(sorted(manifest.domains))
raise ValueError(f"unknown domain {domain!r}; the manifest domains are: {known}")
def _ends(pairs: Iterable[str]) -> Tuple[Tuple[str, ...], Tuple[str, ...]]:
out: List[str] = []
inc: List[str] = []
for pair in pairs:
src, _, dst = pair.partition("->")
if src == dom:
out.append(dst)
if dst == dom:
inc.append(src)
return tuple(sorted(out)), tuple(sorted(inc))
outgoing, incoming = _ends(manifest.graph_allowed)
lazy_out, lazy_in = _ends(manifest.lazy_only)
return DomainDependencies(
dom, manifest.domains[dom], outgoing, incoming, lazy_out, lazy_in,
)
# ---------------------------------------------------------------------------
# facade_consumers
# ---------------------------------------------------------------------------
@dataclass(frozen=True)
class FacadeConsumer:
facade: str # the facade module path
consumer: str # the module importing through the facade
name: str # the imported name ("" = the facade module itself, "*" = star)
line: int
def _population_resolver(modules: Dict[str, str]):
dotted_map = {_module_dotted(path): path for path in modules}
def resolve(dotted: str) -> str | None:
parts = dotted.split(".")
for i in range(len(parts), 0, -1):
candidate = ".".join(parts[:i])
if candidate in dotted_map:
return dotted_map[candidate]
return None
return resolve
def facade_reexports(
repo_root: pathlib.Path,
manifest: DomainManifest | None = None,
) -> Dict[str, Dict[str, str]]:
"""``facade path -> {exported name: leaf path}`` over the manifest population.
Same convention the generated facade inventory pins: a top-level
``from <population module> import ...`` statement carrying the
``noqa: F401`` marker declares re-export bindings.
"""
root = pathlib.Path(repo_root)
manifest = manifest or load_domain_manifest(root)
resolve = _population_resolver(manifest.modules)
facades: Dict[str, Dict[str, str]] = {}
for path in sorted(manifest.modules):
source_path = root / path
if not source_path.is_file():
continue
source = source_path.read_text(encoding="utf-8")
lines = source.splitlines()
try:
tree = ast.parse(source, filename=path)
except SyntaxError:
continue
for node in tree.body:
if not isinstance(node, ast.ImportFrom):
continue
end = getattr(node, "end_lineno", node.lineno) or node.lineno
if not any(
_NOQA_F401.search(lines[lineno - 1])
for lineno in range(node.lineno, min(end, len(lines)) + 1)
):
continue
base = _resolve_relative_import(
pathlib.PurePosixPath(path), node.module or "", int(node.level or 0),
)
for alias in node.names:
if alias.name == "*":
leaf = resolve(base)
else:
leaf = resolve(f"{base}.{alias.name}") or resolve(base)
if leaf is None or leaf == path:
continue
exported = alias.asname or alias.name
facades.setdefault(path, {})[exported] = leaf
return facades
def facade_consumers(repo_root: pathlib.Path, sym: str) -> Tuple[FacadeConsumer, ...]:
"""Who imports through a facade — for a facade module, or one re-exported name.
``sym`` may be a facade module (path or dotted) — every import of that
facade across the population is a consumer row — or a bare re-exported
name — the rows narrow to ``from <facade> import <name>`` sites of the
facades that re-export it. Attribute access on a plain module import
(``import ouroboros.llm`` then ``llm.chat``) is deliberately out of scope:
only import statements are counted.
"""
root = pathlib.Path(repo_root)
manifest = load_domain_manifest(root)
reexports = facade_reexports(root, manifest)
text = str(sym or "").strip().replace("\\", "/")
if not text:
raise ValueError("facade_consumers requires a facade module or a re-exported name")
name_filter = ""
if text in manifest.modules or "/" in text or text.endswith(".py"):
targets = {text} if text in reexports else set()
if not targets:
raise ValueError(
f"{text} is not a facade module (no top-level noqa:F401 re-exports); "
"see docs/v7next/FACADE_INVENTORY.md for the facade list"
)
else:
dotted_map = {_module_dotted(path): path for path in reexports}
if text in dotted_map:
targets = {dotted_map[text]}
else:
targets = {facade for facade, exports in reexports.items() if text in exports}
name_filter = text
if not targets:
raise ValueError(
f"no facade re-exports a name or matches a module {text!r}; "
"facade_consumers answers about noqa:F401 facade bindings"
)
resolve = _population_resolver(manifest.modules)
rows: List[FacadeConsumer] = []
for path in sorted(manifest.modules):
source_path = root / path
if path in targets or not source_path.is_file():
continue
try:
tree = ast.parse(source_path.read_text(encoding="utf-8"), filename=path)
except SyntaxError:
continue
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for alias in node.names:
resolved = resolve(alias.name)
if resolved in targets and not name_filter:
rows.append(FacadeConsumer(resolved, path, "", node.lineno))
elif isinstance(node, ast.ImportFrom):
base = _resolve_relative_import(
pathlib.PurePosixPath(path), node.module or "", int(node.level or 0),
)
for alias in node.names:
if alias.name == "*":
resolved = resolve(base)
if resolved in targets and not name_filter:
rows.append(FacadeConsumer(resolved, path, "*", node.lineno))
continue
base_module = resolve(base)
named_module = resolve(f"{base}.{alias.name}")
if named_module in targets and named_module != base_module:
# ``from ouroboros import llm`` — the facade module itself.
if not name_filter:
rows.append(FacadeConsumer(named_module, path, "", node.lineno))
continue
if base_module in targets:
if name_filter and alias.name != name_filter:
continue
rows.append(FacadeConsumer(base_module, path, alias.name, node.lineno))
return tuple(sorted(rows, key=lambda r: (r.facade, r.consumer, r.line, r.name)))
# ---------------------------------------------------------------------------
# persistence_entities_written_by
# ---------------------------------------------------------------------------
@dataclass(frozen=True)
class PersistenceWrite:
entity: str # the row's Path cell (entity label, backticks kept)
section: str # the "## ..." section the row lives in
writer_cell: str # the row's raw Writer cell
matched: str # the token of ``sym`` that matched the writer cell
def _persistence_rows(repo_root: pathlib.Path) -> List[Tuple[str, str, str]]:
"""Every ``(section, entity_cell, writer_cell)`` row of docs/PERSISTENCE.md."""
text = _read_carrier(repo_root, PERSISTENCE_DOC_RELPATH)
rows: List[Tuple[str, str, str]] = []
section = ""
header: List[str] = []
for line in text.splitlines():
if line.startswith("## "):
section = line[3:].strip()
header = []
continue
if not line.startswith("|"):
header = []
continue
cells = _split_md_row(line)
if [c.lower() for c in cells[:2]] == ["path", "writer"]:
header = cells
continue
if header and set(line.replace("|", "").strip()) <= {"-", " ", ":"}:
continue
if header and len(cells) >= 2:
rows.append((section, cells[0], cells[1]))
if not rows:
raise ValueError(
f"{PERSISTENCE_DOC_RELPATH} carries no Path|Writer table rows; "
"the persistence inventory is the carrier this query reads"
)
return rows
def persistence_entities_written_by(
repo_root: pathlib.Path, sym: str,
) -> Tuple[PersistenceWrite, ...]:
"""Durable entities whose PERSISTENCE.md writer cell names ``sym``.
``sym`` may be a writer module (path or dotted — matched against the
backticked module spans of the Writer column) or a bare function name
(word-matched inside the Writer cell prose, e.g. ``save_settings``).
"""
text = str(sym or "").strip().replace("\\", "/")
if not text:
raise ValueError("persistence_entities_written_by requires a writer module or name")
if "." in text and "/" not in text and not text.endswith(".py"):
candidate = text.replace(".", "/") + ".py"
manifest = load_domain_manifest(repo_root)
if candidate in manifest.modules:
text = candidate
is_path = "/" in text or text.endswith(".py")
rows: List[PersistenceWrite] = []
for section, entity, writer in _persistence_rows(repo_root):
if is_path:
spans = _BACKTICK_SPAN.findall(writer)
if not any(span == text or span.endswith("/" + text) for span in spans):
continue
elif text not in _WORD.findall(writer):
continue
rows.append(PersistenceWrite(entity, section, writer, text))
return tuple(rows)
# ---------------------------------------------------------------------------
# protected_contracts_affected
# ---------------------------------------------------------------------------
@dataclass(frozen=True)
class ContractImpact:
contract: str # the frozen-contract row label
path: str # the changed path that hits the row
role: str # owner | anchor
@dataclass(frozen=True)
class ProtectedImpact:
paths: Tuple[str, ...] # normalized changed paths considered
protected: Tuple[Any, ...] # runtime_mode_policy.ProtectedPath rows
contracts: Tuple[ContractImpact, ...] # frozen-contract rows affected
def _frozen_contract_rows(repo_root: pathlib.Path) -> List[Tuple[str, str, str]]:
"""``(label, file, role)`` triples of the generated frozen-contracts inventory."""
text = _read_carrier(repo_root, FROZEN_INVENTORY_RELPATH)
triples: List[Tuple[str, str, str]] = []
for line in text.splitlines():
if line.startswith("- browser-envelope prose owners:"):
for span in _BACKTICK_SPAN.findall(line):
triples.append(("browser-envelope ABI", span, "owner"))
continue
if not line.startswith("|"):
continue
cells = _split_md_row(line)
if len(cells) < 4 or not cells[0].isdigit():
continue
label_match = _BACKTICK_SPAN.search(cells[1])
label = (label_match.group(1) if label_match else cells[1]).replace("\\|", "|")
for cell, role in ((cells[2], "owner"), (cells[3], "anchor")):
for span in _BACKTICK_SPAN.findall(cell):
file_part = span.replace("\\|", "|").split("::", 1)[0].split(" ", 1)[0]
if file_part:
triples.append((label, file_part, role))
if not triples:
raise ValueError(
f"{FROZEN_INVENTORY_RELPATH} carries no contract rows; regenerate the "
"inventory (scripts/regenerate_inventories.py) before asking for impact"
)
return triples
def paths_from_diff(diff: str | Iterable[str]) -> Tuple[str, ...]:
"""Changed paths from a unified diff text, or a pass-through path iterable."""
from ouroboros.runtime_mode_policy import normalize_repo_path
if isinstance(diff, str):
raw: List[str] = []
if _DIFF_GIT.search(diff) or _DIFF_FILE.search(diff):
for a_path, b_path in _DIFF_GIT.findall(diff):
raw.extend((a_path, b_path))
raw.extend(_DIFF_FILE.findall(diff))
raw.extend(_DIFF_RENAME.findall(diff))
else:
raw.extend(part for part in re.split(r"[,\s]+", diff) if part)
candidates = raw
else:
candidates = [str(p) for p in diff]
normalized = {
normalize_repo_path(p)
for p in candidates
if p and p != "/dev/null" and not p.startswith("/dev/")
}
return tuple(sorted(p for p in normalized if p and p != "."))
def protected_contracts_affected(
repo_root: pathlib.Path, diff: str | Iterable[str],
) -> ProtectedImpact:
"""Protected surfaces and frozen contracts a change set touches.
``diff`` is a unified diff text or an iterable of changed repo-relative
paths. Protected categories come from the ``runtime_mode_policy``
inventories (safety-critical / frozen-contract / release-invariant); the
contract rows come from the generated frozen-contracts inventory — a
changed path that is an owner or anchor file of a row names that contract.
"""
from ouroboros.runtime_mode_policy import protected_paths_in
paths = paths_from_diff(diff)
if not paths:
raise ValueError(
"protected_contracts_affected received no changed paths; pass a "
"unified diff or a comma/space-separated repo-relative path list"
)
protected = tuple(protected_paths_in(paths))
contracts: List[ContractImpact] = []
triples = _frozen_contract_rows(repo_root)
for path in paths:
for label, file_part, role in triples:
if file_part == path:
contracts.append(ContractImpact(label, path, role))
deduped = tuple(sorted(set(contracts), key=lambda c: (c.contract, c.path, c.role)))
return ProtectedImpact(paths=paths, protected=protected, contracts=deduped)
# ---------------------------------------------------------------------------
# The query_code op=architecture renderer
# ---------------------------------------------------------------------------
def architecture_fact_rows(
repo_root: pathlib.Path,
query: str,
*,
inventory: CodeInventory | None = None,
) -> List[str]:
"""Render one architecture fact as compact tool rows.
``query`` is ``"<fact> <argument>"`` where fact is one of
``ARCHITECTURE_FACTS``; for ``protected_contracts_affected`` the argument
is a comma/space-separated changed-path list (or a pasted unified diff).
"""
text = str(query or "").strip()
fact, _, arg = text.partition(" ")
fact = fact.strip().lower()
arg = arg.strip()
if fact not in ARCHITECTURE_FACTS:
raise ValueError(
"op=architecture takes query='<fact> <argument>' with fact one of: "
+ ", ".join(ARCHITECTURE_FACTS)
)
if not arg:
raise ValueError(f"architecture fact {fact} requires an argument after the fact name")
root = pathlib.Path(repo_root)
if fact == "owner_of":
owners = owner_of(root, arg, inventory=inventory)
if not owners:
return [f"{arg}: no domain owner — not in the runtime module population "
f"({DOMAIN_MANIFEST_RELPATH})"]
return [
f"{row.module} -> {row.domain} ({row.domain_title}) [{row.via}]"
for row in owners
]
if fact == "domain_dependencies":
deps = domain_dependencies(root, arg)
return [
f"{deps.domain} ({deps.title}) — manifest-baseline strict edges",
f"imports: {', '.join(deps.outgoing) or '—'}",
f"imported by: {', '.join(deps.incoming) or '—'}",
f"lazy-only imports: {', '.join(deps.lazy_outgoing) or '—'}",
f"lazy-only imported by: {', '.join(deps.lazy_incoming) or '—'}",
]
if fact == "facade_consumers":
rows = facade_consumers(root, arg)
if not rows:
return [f"{arg}: facade has no import-statement consumers in the population"]
return [
f"{row.consumer}:{row.line} imports "
+ (f"{row.name} from {row.facade}" if row.name else f"{row.facade}")
for row in rows
]
if fact == "persistence_entities_written_by":
writes = persistence_entities_written_by(root, arg)
if not writes:
return [f"{arg}: no durable entity in {PERSISTENCE_DOC_RELPATH} names this writer"]
return [
f"{row.entity} [{row.section}] writer: {row.writer_cell}"
for row in writes
]
impact = protected_contracts_affected(root, arg)
rows = [
f"{len(impact.paths)} changed path(s): {len(impact.protected)} protected, "
f"{len(impact.contracts)} frozen-contract row(s) affected"
]
rows.extend(f"{p.path} -> {p.category}" for p in impact.protected)
rows.extend(
f"frozen contract `{c.contract}` ({c.role}: {c.path})" for c in impact.contracts
)
return rows

View file

@ -75,6 +75,7 @@ D20 = "Presence"
"ouroboros/cli.py" = "D18"
"ouroboros/client_surface.py" = "D11"
"ouroboros/code_intelligence.py" = "D05"
"ouroboros/code_intelligence_architecture.py" = "D05"
"ouroboros/code_search_rg.py" = "D05"
"ouroboros/colab_bootstrap.py" = "D12"
"ouroboros/commit_admission.py" = "D06"

View file

@ -28,6 +28,7 @@ _OPS = (
"impact",
"structural",
"digest",
"architecture",
)
_MAX_LIMIT = 200
# Structural walks read every candidate file, so bound them for an arbitrary
@ -371,7 +372,22 @@ def _query_code(
offset = max(0, int(offset or 0))
try:
if op == "structural":
if op == "architecture":
# Architecture facts (CPL-3) are defined over the Ouroboros repo's
# pinned inventories — the manifest/inventory carriers live in the
# code roots, never in a skill payload or an external target.
if normalized_root not in ("active_workspace", "system_repo"):
return (
"⚠️ TOOL_ARG_ERROR (query_code): op architecture requires "
"root=active_workspace or system_repo."
)
from ouroboros.code_intelligence_architecture import architecture_fact_rows
try:
rows = architecture_fact_rows(repo_root, query)
except ValueError as exc:
return f"⚠️ TOOL_ARG_ERROR (query_code): {exc}"
elif op == "structural":
rows = _structural(
ctx, repo_root, query, scoped_path, str(lang or "any"), limit, binding
)
@ -470,11 +486,16 @@ def get_tools() -> List[ToolEntry]:
"...); op=structural (node-type queries) is polyglot too — tree-sitter for every supported "
"language (Python/JS/TS/Go/Rust/Java/Ruby/C/C++/C#/PHP/Kotlin/Swift/Scala/Lua/Bash), with a "
"visible structural_unavailable:<lang> marker when a grammar is missing (Python also has a "
"stdlib-ast fallback). Returns compact file:line anchors and signatures/snippets, never full bodies."
"stdlib-ast fallback). op=architecture answers over the Ouroboros repo's pinned inventories "
"(domain manifest, facade/persistence/frozen-contract carriers): query='<fact> <argument>' with "
"fact one of owner_of, domain_dependencies, facade_consumers, persistence_entities_written_by, "
"protected_contracts_affected (argument = path/symbol, domain id, facade/name, writer, or a "
"comma-separated changed-path list). Returns compact file:line anchors and signatures/snippets, "
"never full bodies."
),
"parameters": {"type": "object", "properties": {
"op": {"type": "string", "enum": list(_OPS), "description": "Operation: relevant_files (where to look), digest (whole-repo map), symbols, definition, references, callers, callees, impact, structural."},
"query": {"type": "string", "default": "", "description": "Exact symbol name (definition/references/callers/...), AST node type (structural), or task text (relevant_files). Empty for digest."},
"op": {"type": "string", "enum": list(_OPS), "description": "Operation: relevant_files (where to look), digest (whole-repo map), symbols, definition, references, callers, callees, impact, structural, architecture (domain/facade/persistence/protected facts)."},
"query": {"type": "string", "default": "", "description": "Exact symbol name (definition/references/callers/...), AST node type (structural), task text (relevant_files), or '<fact> <argument>' (architecture). Empty for digest."},
"path": {"type": "string", "default": "", "description": "Optional file/dir scope or definition disambiguator. REQUIRED for root=user_files (the explicit target dir/file, e.g. '/app' or '/app/src'); it is never the whole home."},
"lang": {"type": "string", "enum": ["python", "javascript", "typescript", "go", "rust", "java", "ruby", "c", "cpp", "csharp", "php", "kotlin", "swift", "scala", "lua", "bash", "any"], "default": "any"},
"kind": {"type": "string", "enum": ["function", "async_function", "class", "constant", "any"], "default": "any"},

View file

@ -0,0 +1,333 @@
"""CPL-3 architecture-fact suite (plan §7.3).
Pins the five pure queries of ``ouroboros/code_intelligence_architecture.py``
on the REAL repository carriers — the domain manifest, the generated facade /
frozen-contract inventories, ``docs/PERSISTENCE.md`` and the
``runtime_mode_policy`` protected inventories — plus their completeness
against those carriers (a manifest row, facade row, persistence row or
frozen-contract row the queries cannot reach = red), and the model-facing
seam: the existing ``query_code`` tool's ``op=architecture``.
"""
from __future__ import annotations
import pathlib
import re
import pytest
from ouroboros.code_intelligence_architecture import (
ARCHITECTURE_FACTS,
FROZEN_INVENTORY_RELPATH,
PERSISTENCE_DOC_RELPATH,
_frozen_contract_rows,
_persistence_rows,
architecture_fact_rows,
domain_dependencies,
facade_consumers,
facade_reexports,
load_domain_manifest,
owner_of,
paths_from_diff,
persistence_entities_written_by,
protected_contracts_affected,
)
REPO = pathlib.Path(__file__).resolve().parents[1]
@pytest.fixture(scope="module")
def manifest():
return load_domain_manifest(REPO)
@pytest.fixture(scope="module")
def reexports(manifest):
return facade_reexports(REPO, manifest)
# ---------------------------------------------------------------------------
# owner_of
# ---------------------------------------------------------------------------
def test_owner_of_resolves_a_module_path_a_dotted_module_and_a_symbol():
(llm,) = owner_of(REPO, "ouroboros/llm.py")
assert (llm.domain, llm.via) == ("D02", "module_path")
assert llm.domain_title # the human vocabulary rides along
(state,) = owner_of(REPO, "supervisor.state")
assert (state.module, state.domain, state.via) == (
"supervisor/state.py", "D08", "dotted_module",
)
owners = owner_of(REPO, "protected_path_category")
assert any(
(o.module, o.domain, o.via) == ("ouroboros/runtime_mode_policy.py", "D13", "symbol_definition")
for o in owners
), owners
def test_owner_of_outside_the_population_is_ownerless_not_an_error():
assert owner_of(REPO, "web/modules/chat.js") == ()
assert owner_of(REPO, "docs/ARCHITECTURE.md") == ()
def test_owner_of_covers_every_manifest_module(manifest):
"""Completeness against the manifest: every population row answers with
exactly its pinned domain."""
assert len(manifest.modules) >= 400 # the whole runtime population, not a sample
for path, domain in manifest.modules.items():
rows = owner_of(REPO, path)
assert [(r.module, r.domain) for r in rows] == [(path, domain)], path
# ---------------------------------------------------------------------------
# domain_dependencies
# ---------------------------------------------------------------------------
def test_domain_dependencies_reports_the_manifest_baseline_edges(manifest):
deps = domain_dependencies(REPO, "d02") # case-normalized
assert deps.domain == "D02" and deps.title == manifest.domains["D02"]
assert set(deps.outgoing) == {
d.split("->")[1] for d in manifest.graph_allowed if d.startswith("D02->")
}
assert set(deps.incoming) == {
d.split("->")[0] for d in manifest.graph_allowed if d.endswith("->D02")
}
def test_domain_dependencies_cover_the_whole_direction_matrix(manifest):
"""Completeness: the union of per-domain answers reproduces [graph].allowed
and [graph].lazy_only exactly — both directions."""
strict, lazy = set(), set()
for domain in manifest.domains:
deps = domain_dependencies(REPO, domain)
strict.update(f"{domain}->{dst}" for dst in deps.outgoing)
strict.update(f"{src}->{domain}" for src in deps.incoming)
lazy.update(f"{domain}->{dst}" for dst in deps.lazy_outgoing)
lazy.update(f"{src}->{domain}" for src in deps.lazy_incoming)
assert strict == set(manifest.graph_allowed)
assert lazy == set(manifest.lazy_only)
def test_domain_dependencies_unknown_domain_teaches_the_vocabulary():
with pytest.raises(ValueError, match="unknown domain.*D01"):
domain_dependencies(REPO, "D99")
# ---------------------------------------------------------------------------
# facade_consumers
# ---------------------------------------------------------------------------
def test_facade_scan_matches_the_generated_facade_inventory(reexports):
"""Completeness against the gen/verify-pinned carrier: the runtime scan
finds exactly the facade modules docs/v7next/FACADE_INVENTORY.md pins."""
inventory_text = (REPO / "docs/v7next/FACADE_INVENTORY.md").read_text(encoding="utf-8")
pinned = set()
for line in inventory_text.splitlines():
if line.startswith("| `") and line.count("|") >= 4:
first = line.split("|")[1].strip()
if first.startswith("`") and first.endswith("`"):
pinned.add(first.strip("`"))
assert pinned, "the facade inventory carrier parsed empty"
assert set(reexports) == pinned
def test_facade_module_query_lists_its_import_consumers():
rows = facade_consumers(REPO, "ouroboros/llm.py")
consumers = {r.consumer for r in rows}
assert "ouroboros/agent.py" in consumers # from ouroboros.llm import LLMClient
assert all(r.facade == "ouroboros/llm.py" and r.line > 0 for r in rows)
def test_facade_symbol_query_narrows_to_the_reexported_name(reexports):
rows = facade_consumers(REPO, "add_usage")
assert rows, "add_usage is a re-exported facade binding with real consumers"
for row in rows:
assert row.name == "add_usage"
assert "add_usage" in reexports[row.facade]
assert "ouroboros/loop_llm_call.py" in {r.consumer for r in rows}
def test_facade_query_on_a_non_facade_is_a_teaching_refusal():
with pytest.raises(ValueError, match="not a facade module"):
facade_consumers(REPO, "ouroboros/runtime_mode_policy.py")
with pytest.raises(ValueError, match="no facade re-exports"):
facade_consumers(REPO, "definitely_not_an_exported_name")
# ---------------------------------------------------------------------------
# persistence_entities_written_by
# ---------------------------------------------------------------------------
def test_writer_module_query_names_its_entities():
entities = " | ".join(
r.entity for r in persistence_entities_written_by(REPO, "supervisor/state.py")
)
assert "state/state.json" in entities
ledger = persistence_entities_written_by(REPO, "ouroboros/usage_ledger.py")
assert any("usage_attempts.jsonl" in r.entity for r in ledger)
# A dotted spelling of the same writer answers identically.
assert persistence_entities_written_by(REPO, "supervisor.state") == \
persistence_entities_written_by(REPO, "supervisor/state.py")
def test_writer_function_name_query_matches_writer_prose():
rows = persistence_entities_written_by(REPO, "save_settings")
assert [r.entity for r in rows] == ["`settings.json`"]
def test_persistence_parser_reaches_every_table_row(manifest):
"""Completeness against the carrier: the parser yields one row per
Path|Writer table line of docs/PERSISTENCE.md (nothing silently dropped),
and every exact .py writer span resolves against the tree."""
rows = _persistence_rows(REPO)
text = (REPO / PERSISTENCE_DOC_RELPATH).read_text(encoding="utf-8")
raw_rows = 0
in_table = False
for line in text.splitlines():
if line.startswith("| Path | Writer |"):
in_table = True
continue
if not line.startswith("|"):
in_table = False
continue
if in_table and not set(line.replace("|", "").strip()) <= {"-", " ", ":"}:
raw_rows += 1
assert raw_rows == len(rows) and raw_rows >= 50, (raw_rows, len(rows))
span_re = re.compile(r"`([^`]+\.py)`")
for _section, _entity, writer in rows:
for span in span_re.findall(writer):
if any(ch in span for ch in "*<>"):
continue # glob/placeholder spans are labels, not paths
resolved = (REPO / span).is_file() or any(
module.endswith("/" + span) for module in manifest.modules
) # the doc shortens sibling writers to bare filenames in listings
assert resolved, f"PERSISTENCE.md names a missing writer: {span}"
# ---------------------------------------------------------------------------
# protected_contracts_affected
# ---------------------------------------------------------------------------
def test_protected_diff_names_categories_and_contracts():
diff = (
"diff --git a/ouroboros/gateway/contracts.py b/ouroboros/gateway/contracts.py\n"
"--- a/ouroboros/gateway/contracts.py\n"
"+++ b/ouroboros/gateway/contracts.py\n"
"@@ -1 +1 @@\n-1\n+2\n"
"diff --git a/BIBLE.md b/BIBLE.md\n"
"--- a/BIBLE.md\n+++ b/BIBLE.md\n@@ -1 +1 @@\n-1\n+2\n"
"diff --git a/README.md b/README.md\n"
"--- a/README.md\n+++ b/README.md\n@@ -1 +1 @@\n-1\n+2\n"
)
impact = protected_contracts_affected(REPO, diff)
assert set(impact.paths) == {"ouroboros/gateway/contracts.py", "BIBLE.md", "README.md"}
categories = {(p.path, p.category) for p in impact.protected}
assert ("BIBLE.md", "safety-critical") in categories
assert ("ouroboros/gateway/contracts.py", "frozen-contract") in categories
assert not any(p.path == "README.md" for p in impact.protected)
labels = {c.contract for c in impact.contracts}
assert "gateway/contracts.py" in labels # the §11.1 row itself
assert "ProviderTestRequest" in labels # a contract owned by the touched file
def test_protected_path_list_form_and_release_invariants():
impact = protected_contracts_affected(
REPO, ["supervisor/git_ops_reset.py", "ouroboros/safety.py"],
)
categories = dict((p.path, p.category) for p in impact.protected)
assert categories == {
"supervisor/git_ops_reset.py": "release-invariant",
"ouroboros/safety.py": "safety-critical",
}
def test_every_frozen_contract_row_is_reachable_from_its_owner_file():
"""Completeness against the generated inventory: feeding each row's own
owner file back into the query must name that row."""
triples = _frozen_contract_rows(REPO)
assert len({label for label, _f, _r in triples}) >= 20
for label, file_part, role in triples:
impact = protected_contracts_affected(REPO, [file_part])
assert (label, role) in {(c.contract, c.role) for c in impact.contracts}, (
label, file_part, role,
)
def test_protected_inventories_are_fully_categorized():
from ouroboros.runtime_mode_policy import (
FROZEN_CONTRACT_PATHS,
RELEASE_INVARIANT_PATHS,
SAFETY_CRITICAL_PATHS,
)
every = sorted(SAFETY_CRITICAL_PATHS | FROZEN_CONTRACT_PATHS | RELEASE_INVARIANT_PATHS)
impact = protected_contracts_affected(REPO, every)
assert {p.path for p in impact.protected} == set(every)
def test_paths_from_diff_accepts_lists_diffs_and_separated_strings():
assert paths_from_diff("a.py, b.py c.py") == ("a.py", "b.py", "c.py")
assert paths_from_diff(["./x/y.py", "x/y.py"]) == ("x/y.py",)
with pytest.raises(ValueError, match="no changed paths"):
protected_contracts_affected(REPO, " ")
# ---------------------------------------------------------------------------
# The model-facing seam: query_code op=architecture
# ---------------------------------------------------------------------------
def _ctx(tmp_path):
from ouroboros.tools.registry import ToolContext
return ToolContext(repo_dir=REPO, drive_root=tmp_path)
def test_query_code_architecture_op_serves_the_five_facts(tmp_path):
from ouroboros.tools.query_code import _query_code
out = _query_code(_ctx(tmp_path), op="architecture", query="owner_of ouroboros/llm.py")
assert "D02" in out and "module_path" in out
out = _query_code(_ctx(tmp_path), op="architecture", query="domain_dependencies D02")
assert "imports:" in out and "imported by:" in out
out = _query_code(
_ctx(tmp_path), op="architecture",
query="protected_contracts_affected BIBLE.md, ouroboros/gateway/contracts.py",
)
assert "safety-critical" in out and "frozen contract" in out
def test_query_code_architecture_op_refuses_bad_facts_and_foreign_roots(tmp_path):
from ouroboros.tools.query_code import _query_code
out = _query_code(_ctx(tmp_path), op="architecture", query="who_owns ouroboros/llm.py")
assert "TOOL_ARG_ERROR" in out and "owner_of" in out # the refusal teaches the facts
out = _query_code(_ctx(tmp_path), op="architecture", query="owner_of")
assert "TOOL_ARG_ERROR" in out and "argument" in out
out = _query_code(
_ctx(tmp_path), op="architecture", query="owner_of ouroboros/llm.py",
root="user_files", path="/tmp",
)
# A non-code root never serves architecture facts: either the binding layer
# or the op's own root guard refuses, typed.
assert ("TOOL_ARG_ERROR" in out or "TOOL_ACCESS_BLOCKED" in out)
assert "D02" not in out
def test_architecture_fact_vocabulary_is_closed():
assert ARCHITECTURE_FACTS == (
"owner_of",
"domain_dependencies",
"facade_consumers",
"persistence_entities_written_by",
"protected_contracts_affected",
)
with pytest.raises(ValueError, match="owner_of"):
architecture_fact_rows(REPO, "unknown_fact x")
assert FROZEN_INVENTORY_RELPATH.endswith("FROZEN_CONTRACTS_INVENTORY.md")