Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions loopx/semantics/inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,23 @@
SOURCE_SUFFIXES = (".py", ".ts")
SKIP_PARTS = frozenset({"__pycache__", "node_modules"})

# Constants that exist only to read or reject vocabulary retired by a one-way
# migration. Their values are not current semantic ownership, so the repository
# map must not republish them: listing them misstates which module owns the
# vocabulary today and copies retired hierarchy tokens into a derived artifact
# outside the migration boundary the peer-agent guard enforces. Each pair is
# explicit rather than name-matched so a new carrier cannot be hidden by
# accident, and the skipped pairs stay visible in the rendered inventory.
RETIRED_VOCABULARY_CARRIERS = frozenset(
{
("loopx/control_plane/agents/legacy_migration.py", "LEGACY_HIERARCHY_ROLES"),
(
"loopx/control_plane/todos/contract.py",
"TODO_REMOVED_REVIEW_CONTINUATION_POLICY_VALUES",
),
}
)

UPPER_NAME = re.compile(r"^[A-Z][A-Z0-9_]*$")
SCHEMA_VERSION_NAME = re.compile(r"SCHEMA_VERSION$")
# Names that are a per-module convention rather than shared vocabulary: every
Expand Down Expand Up @@ -253,6 +270,10 @@ def _sorted_entries(entries: list[dict[str, Any]]) -> list[dict[str, Any]]:
return sorted(entries, key=lambda entry: (entry["module"], entry["name"]))


def _is_retired_vocabulary(entry: dict[str, Any]) -> bool:
return (entry.get("module"), entry.get("name")) in RETIRED_VOCABULARY_CARRIERS


def build_inventory(
repo_root: Path, root: str = DEFAULT_ROOT, sources: list[SourceFile] | None = None
) -> dict[str, Any]:
Expand All @@ -275,6 +296,12 @@ def build_inventory(
const_arrays.extend(facts["const_arrays"])
string_constants.extend(facts["string_constants"])

enums = [entry for entry in enums if not _is_retired_vocabulary(entry)]
closed_sets = [entry for entry in closed_sets if not _is_retired_vocabulary(entry)]
literal_aliases = [entry for entry in literal_aliases if not _is_retired_vocabulary(entry)]
const_arrays = [entry for entry in const_arrays if not _is_retired_vocabulary(entry)]
string_constants = [entry for entry in string_constants if not _is_retired_vocabulary(entry)]

multi_value = multi_value_carriers(
enums=enums,
closed_sets=closed_sets,
Expand Down Expand Up @@ -307,6 +334,9 @@ def build_inventory(
"Structural map only. Consumer counts are printed by the generator's --report "
"and are not committed. Single-module string constants are counted, not listed."
),
"retired_vocabulary_excluded": _sorted_entries(
{"module": module, "name": name} for module, name in RETIRED_VOCABULARY_CARRIERS
),
"python_enums": _sorted_entries(enums),
"python_closed_sets": _sorted_entries(closed_sets),
"python_literal_aliases": _sorted_entries(literal_aliases),
Expand Down
8 changes: 5 additions & 3 deletions loopx/semantics/inventory_v0.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
"root": "loopx",
"generator": "scripts/generate_semantic_inventory.py",
"advisory": "Structural map only. Consumer counts are printed by the generator's --report and are not committed. Single-module string constants are counted, not listed.",
"retired_vocabulary_excluded": [
{"module": "loopx/control_plane/agents/legacy_migration.py", "name": "LEGACY_HIERARCHY_ROLES"},
{"module": "loopx/control_plane/todos/contract.py", "name": "TODO_REMOVED_REVIEW_CONTINUATION_POLICY_VALUES"}
],
"python_enums": [
{"name": "BenchmarkContinuationDecision", "module": "loopx/capabilities/benchmark_toolkit/continuation.py", "values": ["continue", "stop_complete", "stop_progress_regression", "stop_prompt_mismatch", "stop_round_limit", "stop_task_shape_mismatch", "stop_time_budget"]},
{"name": "NetworkRequestScope", "module": "loopx/capabilities/benchmark_toolkit/integrity.py", "values": ["none", "loopback", "external"]},
Expand Down Expand Up @@ -325,7 +329,6 @@
{"name": "CAPABILITY_OWNER_GATE_HINTS", "module": "loopx/control_plane/agents/capability_gate.py", "container": "set", "values": ["credentials", "production_access"]},
{"name": "DELIVERY_WORKSPACE_IDENTITY_KINDS", "module": "loopx/control_plane/agents/delivery_workspace.py", "container": "frozenset", "values": ["git_repository", "local_goal"]},
{"name": "DELIVERY_WORKSPACE_KINDS", "module": "loopx/control_plane/agents/delivery_workspace.py", "container": "frozenset", "values": ["canonical_checkout", "independent_git_worktree", "local_goal_workspace"]},
{"name": "LEGACY_HIERARCHY_ROLES", "module": "loopx/control_plane/agents/legacy_migration.py", "container": "set", "values": ["primary-agent", "side-agent"]},
{"name": "AGENT_PROFILE_FIELDS", "module": "loopx/control_plane/agents/profile.py", "container": "set", "values": ["schema_version", "agent_id", "profile_role", "scope_summary", "default_task_classes", "vision_requirement", "preferred_action_kinds", "avoid_action_kinds"]},
{"name": "AGENT_PROFILE_HIERARCHY_ROLES", "module": "loopx/control_plane/agents/profile.py", "container": "set", "values": ["leader", "manager", "supervisor", "worker"]},
{"name": "AGENT_PROFILE_VISION_REQUIREMENTS", "module": "loopx/control_plane/agents/profile.py", "container": "set", "values": ["optional", "required"]},
Expand Down Expand Up @@ -433,7 +436,6 @@
{"name": "TODO_DECISION_SCOPE_KIND_VALUES", "module": "loopx/control_plane/todos/contract.py", "container": "set", "values": ["private_read", "write_scope", "resource", "production", "public_claim", "direction", "other"]},
{"name": "TODO_LEGACY_TERMINAL_STATUS_VALUES", "module": "loopx/control_plane/todos/contract.py", "container": "set", "values": ["completed", "closed", "archived"]},
{"name": "TODO_MONITOR_METADATA_FIELDS", "module": "loopx/control_plane/todos/contract.py", "container": "tuple", "values": ["target_key", "monitor_effect_id", "cadence", "next_due_at", "expires_at", "last_checked_at", "result_hash", "consecutive_no_change", "material_change", "material_change_generation", "max_no_change_before_replan", "watch_only"]},
{"name": "TODO_REMOVED_REVIEW_CONTINUATION_POLICY_VALUES", "module": "loopx/control_plane/todos/contract.py", "container": "set", "values": ["primary_review", "review_handoff"]},
{"name": "TODO_GATE_BLOCKING_STATES", "module": "loopx/control_plane/todos/decision_scope.py", "container": "frozenset", "values": ["gate_targets_todo", "gate_covers_action", "projection_repair_required"]},
{"name": "TODO_FRONTIER_DEADLINE_LANES", "module": "loopx/control_plane/todos/frontier_deadline.py", "container": "tuple", "values": ["current_agent_claimed_monitor_items", "monitor_open_items", "gate_open_items", "deferred_resume_candidates", "current_agent_deferred_resume_candidates", "resume_blocked_items", "current_agent_monitor_blocked_resume_candidates", "current_agent_handoff_gates"]},
{"name": "FRONTIER_REVISION_FIELDS", "module": "loopx/control_plane/todos/frontier_revision.py", "container": "tuple", "values": ["todo_id", "status", "done", "title", "text", "task_class", "claimed_by", "bound_agent", "blocks_agent", "excluded_agents", "priority", "action_kind", "task_domain", "task_repository", "capability_binding_ref", "required_capabilities", "target_capabilities", "target_key", "continuation_policy", "removed_continuation_policy", "decision_scope", "required_decision_scopes", "decision_outcome", "replan_obligation_id", "unblocks_todo_id", "depends_on_todo_id", "depends_on_todo_ids", "resume_when", "no_followup", "successor_todo_ids", "completion_continuation"]},
Expand Down Expand Up @@ -902,7 +904,7 @@
"summary": {
"source_files": 1181,
"python_enums": 103,
"python_closed_sets": 493,
"python_closed_sets": 491,
"python_literal_aliases": 8,
"typescript_const_arrays": 40,
"named_string_constants": 2059,
Expand Down
46 changes: 46 additions & 0 deletions tests/architecture/test_semantic_inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,52 @@ def test_render_is_deterministic_valid_json(repo: Path) -> None:
assert ' {"name": "Kind", "module": "loopx/a.py", "values": ["one", "two"]}' in rendered


@pytest.fixture
def retirement_repo(tmp_path: Path) -> Path:
"""A tree where retired migration vocabulary sits next to live vocabulary."""
_write(
tmp_path,
"loopx/control_plane/agents/legacy_migration.py",
'LEGACY_HIERARCHY_ROLES = {"primary-agent", "side-agent"}\n',
)
_write(
tmp_path,
"loopx/control_plane/todos/contract.py",
'TODO_REMOVED_REVIEW_CONTINUATION_POLICY_VALUES = {"primary_review", "review_handoff"}\n'
'TODO_DECISION_SCOPE_KIND_VALUES = {"private_read", "write_scope"}\n',
)
subprocess.run(["git", "init", "-q", str(tmp_path)], check=True)
subprocess.run(["git", "-C", str(tmp_path), "add", "loopx"], check=True)
return tmp_path


def test_retired_migration_vocabulary_stays_out_of_the_map(retirement_repo: Path) -> None:
"""A migration module's constants describe retired vocabulary, not ownership.

Republishing them would copy hierarchy tokens into a derived artifact
outside the peer migration boundary the repository guard enforces, and
would name a migration module as the current owner of a retired vocabulary.
The declared exclusions stay visible so a reviewer sees the omission.
"""
inventory = build_inventory(retirement_repo)
assert [entry["name"] for entry in inventory["python_closed_sets"]] == [
"TODO_DECISION_SCOPE_KIND_VALUES"
]
assert inventory["retired_vocabulary_excluded"] == [
{
"module": "loopx/control_plane/agents/legacy_migration.py",
"name": "LEGACY_HIERARCHY_ROLES",
},
{
"module": "loopx/control_plane/todos/contract.py",
"name": "TODO_REMOVED_REVIEW_CONTINUATION_POLICY_VALUES",
},
]
duplicates = inventory["duplicate_definitions"]
assert duplicates["multi_value_twins"] == []
assert duplicates["multi_value_forks"] == []


def test_committed_inventory_matches_the_tree() -> None:
result = subprocess.run(
[sys.executable, str(REPO_ROOT / "scripts" / "generate_semantic_inventory.py"), "--check"],
Expand Down
Loading