Skip to content
Merged
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
6 changes: 3 additions & 3 deletions examples/semantic-vocabulary-drift-smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,16 +145,16 @@
TWIN_ROOT_ANCHOR = "loopx/control_plane"
TWIN_BUDGET_ANCHOR = 43
BUDGET_ANCHOR = {
"same_runtime_forks": 25,
"same_runtime_fork_definitions": 58,
"same_runtime_forks": 20,
"same_runtime_fork_definitions": 47,
"conflicting_values": 18,
"conflicting_definitions": 59,
"schema_version_same_runtime_forks": 7,
"multi_value_twins": 19,
"multi_value_forks": 4,
"multi_value_forks_semantic": 3,
"multi_value_fork_definitions": 10,
"same_runtime_forks_semantic": 18,
"same_runtime_forks_semantic": 13,
"conflicting_values_semantic": 0,
}
# Budgets for the legacy should-run decision fields, anchored the same way so a
Expand Down
6 changes: 3 additions & 3 deletions loopx/semantics/vocabulary_v0.json
Original file line number Diff line number Diff line change
Expand Up @@ -886,15 +886,15 @@
},
"inventory_ratchets": {
"meaning": "Counts read from the generated inventory. A same-runtime fork is one constant name with one value defined in two or more modules of the same runtime; a conflicting value is one name with different values. Both the number of affected names and the number of definitions are budgets, so a third spelling of an already-conflicting name is still a regression.",
"same_runtime_forks": 25,
"same_runtime_fork_definitions": 58,
"same_runtime_forks": 20,
"same_runtime_fork_definitions": 47,
"conflicting_values": 18,
"conflicting_definitions": 59,
"schema_version_same_runtime_forks": 7,
"multi_value_twins": 19,
"multi_value_forks": 4,
"multi_value_fork_definitions": 10,
"same_runtime_forks_semantic": 18,
"same_runtime_forks_semantic": 13,
"conflicting_values_semantic": 0,
"multi_value_meaning": "Enums, named closed sets, Literal aliases, and TypeScript as-const arrays are vocabulary exactly as a NAME = \"value\" constant is, so they get the same collision rule. One name defined in two modules with identical values is a twin; with different values it is a fork. The semantic multi-value-fork budget excludes only names declared in scope_declarations.",
"multi_value_forks_note": "The 4 counted forks include SOURCE_SURFACES, whose four definitions are four CLI commands each listing its own data sources; that is bounded-context reuse of one name, not drift. It stays in the budget until M0.5 adds a scope field (RFC Section 5) and must not be removed by renaming.",
Expand Down
39 changes: 24 additions & 15 deletions loopx/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,23 @@
StatusCollectionContext,
collect_status as _collect_status_read_model,
)
from .control_plane.status.active_state_projection import (
STATE_EVENT_LOG_BASENAME,
)
from .control_plane.status.contract_projection import (
STATUS_CONTRACT_RELOAD_HINT,
)
from .control_plane.status.goal_attention_projection import (
PLANNED_CONTROLLER_OPT_IN_RECOMMENDED_ACTION,
)
# Refs #4447: one definition for this vocabulary. The control_plane projection
# owns it because it feeds the attention-queue read model; this module keeps
# re-exporting the name for existing callers.
# owns it because it feeds the monitor/attention read models; this module keeps
# re-exporting each name for existing callers instead of restating its value.
from .control_plane.status.monitor_display_projection import (
MONITOR_DISPLAY_FALLBACK_ACTION,
MONITOR_DISPLAY_STOP_CONDITION,
MONITOR_SIGNAL_WAITING_ON,
)
from .control_plane.status.registry_health_projection import (
SOURCE_REGISTRY_SHADOW_FINDINGS,
)
Expand Down Expand Up @@ -224,11 +238,18 @@
"todo_projection_sort_key": "loopx.control_plane.todos.todo_summary",
"normalize_todo_task_class": "loopx.control_plane.todos.contract",
"todo_item_is_expired_monitor": "loopx.control_plane.todos.todo_semantics",
# Refs #4447: single-sourced monitor/status vocabulary. Each name was already
# defined here and again in the control_plane projection that owns it; these
# entries keep the facade exporting one definition instead of a second copy.
"MONITOR_DISPLAY_STOP_CONDITION": "loopx.control_plane.status.monitor_display_projection",
"MONITOR_DISPLAY_FALLBACK_ACTION": "loopx.control_plane.status.monitor_display_projection",
"STATUS_CONTRACT_RELOAD_HINT": "loopx.control_plane.status.contract_projection",
"STATE_EVENT_LOG_BASENAME": "loopx.control_plane.status.active_state_projection",
"PLANNED_CONTROLLER_OPT_IN_RECOMMENDED_ACTION": "loopx.control_plane.status.goal_attention_projection",
}


STATUS_NEUTRAL_CLASSIFICATIONS = HISTORY_STATUS_NEUTRAL_CLASSIFICATIONS
STATE_EVENT_LOG_BASENAME = "events.jsonl"
STATUS_CONTROL_PLANE_CONTEXT_LIMIT = 20
AGENT_LANE_PROGRESS_SCOPE = "agent_lane"
REGISTRY_WAITING_ON_OVERRIDES = {
Expand All @@ -241,18 +262,9 @@
"await_",
"external_evidence_observation_",
)
MONITOR_SIGNAL_WAITING_ON = "monitor_signal"
MONITOR_DISPLAY_SCHEMA_VERSION = "monitor_quiet_display_v0"
MONITOR_DISPLAY_STOP_CONDITION = (
"stop until a material monitor transition, regression, or concrete blocker appears"
)
MONITOR_DISPLAY_FALLBACK_ACTION = (
"No immediate agent work; keep the monitor quiet until a material monitor "
"transition, regression, or concrete blocker appears."
)
STATUS_CONTRACT_SCHEMA_VERSION = 2
MINIMUM_DASHBOARD_STATUS_CONTRACT_SCHEMA_VERSION = 2
STATUS_CONTRACT_RELOAD_HINT = "scripts/macos-dashboard-launchagent.sh restart"
STATUS_CONTRACT_SIGNAL_LIMIT = 3
MONITOR_WRITEBACK_CONTRACT_SCHEMA_VERSION = _MONITOR_WRITEBACK_CONTRACT_SCHEMA_VERSION
EVENT_LEDGER_DECISION_CLASSIFICATIONS = USER_OR_CONTROLLER_CLASSIFICATIONS | {
Expand Down Expand Up @@ -293,9 +305,6 @@
CONNECTED_DELIVERY_ADAPTER_STATUSES = {
"connected-delivery",
}
PLANNED_CONTROLLER_OPT_IN_RECOMMENDED_ACTION = (
"先在 LoopX 完成 operator 判断;同意后项目 Agent 只执行 read-only map dry-run"
)
RUN_COMPACT_FIELDS = RUN_BASE_COMPACT_FIELDS
LIFECYCLE_PRIORITY = (
"controller_ready",
Expand Down
29 changes: 25 additions & 4 deletions tests/architecture/test_control_plane_import_boundaries.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,34 @@ def _public_contract_evidence() -> dict[tuple[str, str], set[str]]:
for root in PUBLIC_CONTRACT_ROOTS:
for path in root.rglob("*.py"):
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
# Aliases such as ``from loopx import status as status_module`` are a
# real consumer of a facade export, reached by attribute access
# rather than a from-import. Without this map the audit would report
# a live consumer as missing evidence.
module_aliases: dict[str, str] = {}
for node in ast.walk(tree):
if not isinstance(node, ast.ImportFrom):
continue
if node.module not in PUBLIC_COMPAT_FACADES:
if not isinstance(node, ast.ImportFrom) or node.level:
continue
module = node.module or ""
for alias in node.names:
evidence.setdefault((node.module, alias.name), set()).add(
# ``from loopx import status`` binds the facade itself; a
# from-import of a facade also binds the facade's exports.
candidate = alias.name if not module else f"{module}.{alias.name}"
if candidate in PUBLIC_COMPAT_FACADES:
module_aliases[alias.asname or alias.name] = candidate
continue
if module in PUBLIC_COMPAT_FACADES:
evidence.setdefault((module, alias.name), set()).add(
str(path.relative_to(REPOSITORY_ROOT))
)
if not module_aliases:
continue
for node in ast.walk(tree):
if not isinstance(node, ast.Attribute) or not isinstance(node.ctx, ast.Load):
continue
facade_name = module_aliases.get(getattr(node.value, "id", ""))
if facade_name is not None:
evidence.setdefault((facade_name, node.attr), set()).add(
str(path.relative_to(REPOSITORY_ROOT))
)
for path in (REPOSITORY_ROOT / "docs").rglob("*.md"):
Expand Down