From 9c9dc6b09dc26a963da490afa72d3e90451a7514 Mon Sep 17 00:00:00 2001 From: Edwin Amirian Date: Wed, 12 Aug 2026 15:28:20 -0700 Subject: [PATCH] refactor: isolate legacy product intelligence runtime --- .env.example | 5 + .../engine/api/legacy_product_intelligence.py | 46 ++++++++ core/engine/api/main.py | 16 ++- core/engine/core/config.py | 7 ++ docs/README.md | 3 + ...-runtime-boundary-v0.8.0-work-packet-v1.md | 80 ++++++++++++++ .../test_intelligence_os_runtime_boundary.py | 100 ++++++++++++++++++ 7 files changed, 248 insertions(+), 9 deletions(-) create mode 100644 core/engine/api/legacy_product_intelligence.py create mode 100644 docs/design/intelligence-os-runtime-boundary-v0.8.0-work-packet-v1.md create mode 100644 tests/test_intelligence_os_runtime_boundary.py diff --git a/.env.example b/.env.example index aaded9d..dd94e47 100644 --- a/.env.example +++ b/.env.example @@ -56,6 +56,11 @@ FIRECRAWL_API_KEY=ace-local-key # (LAN/VPN). Appended to the environment's built-in localhost defaults. # CORS_EXTRA_ORIGINS=http://:5173,http://:5173 +# ACE 0.8 uses Domain Packs plus authorized connectors for intelligence. The old +# ACE-product competitive/community/release/whitespace sentinel engines are retained +# only for migration compatibility and are disabled by default. +# ENABLE_LEGACY_PRODUCT_INTELLIGENCE=false + # Discord (proactive PM notifications) # ACE_DISCORD_BOT_TOKEN=your-bot-token # ACE_DISCORD_USER_ID=your-discord-user-id diff --git a/core/engine/api/legacy_product_intelligence.py b/core/engine/api/legacy_product_intelligence.py new file mode 100644 index 0000000..5e74790 --- /dev/null +++ b/core/engine/api/legacy_product_intelligence.py @@ -0,0 +1,46 @@ +"""Explicit compatibility gate for the pre-Domain-Pack intelligence engines. + +These modules predate the public ``ace.intelligence`` lifecycle and encode ACE-product +competitive concepts directly in the legacy host. They remain import-compatible during the +0.8 migration, but the default Intelligence OS runtime must not compose them implicitly. +""" + +from __future__ import annotations + +import importlib +import logging + +logger = logging.getLogger(__name__) + +LEGACY_PRODUCT_INTELLIGENCE_MODULES = ( + "core.engine.sentinel.engines.community_scanner", + "core.engine.sentinel.engines.competitive_observer", + "core.engine.sentinel.engines.github_release_watcher", + "core.engine.sentinel.engines.whitespace_engine", +) + + +def register_legacy_product_intelligence_engines(*, enabled: bool) -> tuple[str, ...]: + """Register the old ACE-product intelligence engines only by explicit opt-in. + + The returned module names are diagnostic compatibility evidence. Registration remains the + legacy decorators' responsibility; this seam grants no source, model, or execution authority. + """ + + if not enabled: + return () + + logger.warning( + "Legacy product-intelligence engines are enabled. This compatibility surface is not the " + "ACE 0.8 Intelligence lifecycle and should be replaced by a Domain Pack plus authorized " + "connectors." + ) + for module_name in LEGACY_PRODUCT_INTELLIGENCE_MODULES: + importlib.import_module(module_name) + return LEGACY_PRODUCT_INTELLIGENCE_MODULES + + +__all__ = [ + "LEGACY_PRODUCT_INTELLIGENCE_MODULES", + "register_legacy_product_intelligence_engines", +] diff --git a/core/engine/api/main.py b/core/engine/api/main.py index d83a0b6..8e4a38d 100644 --- a/core/engine/api/main.py +++ b/core/engine/api/main.py @@ -14,6 +14,7 @@ from slowapi.util import get_remote_address from starlette.middleware.base import BaseHTTPMiddleware +from core.engine.api.legacy_product_intelligence import register_legacy_product_intelligence_engines from core.engine.core.config import settings from core.engine.core.db import pool from core.engine.version import VERSION @@ -283,12 +284,13 @@ async def lifespan(app: FastAPI): # Phase 7a calibration engine import core.engine.sentinel.engines.calibration_engine # noqa: F401 - # S1 competitive intelligence watchers + community summarizer (Sat 3am Louvain clusters over - # cognify edges → LLM theme summaries the briefing surfaces; MUST be imported here to register, - # explicit-imports-only per the note above). - import core.engine.sentinel.engines.community_scanner # noqa: F401 (S1 competitive intelligence watcher) + # The knowledge-graph community summarizer remains domain-neutral. The older competitor, + # community-source, release-watcher, and whitespace engines are an explicit compatibility + # surface; the default 0.8 runtime receives equivalent meaning from Domain Packs and authorized + # connectors rather than embedding a market-intelligence branch in Core. import core.engine.sentinel.engines.community_summarizer # noqa: F401 (Sat 3am summarizer — MUST import here) - import core.engine.sentinel.engines.competitive_observer # noqa: F401 (S1 competitive intelligence watcher) + + register_legacy_product_intelligence_engines(enabled=settings.enable_legacy_product_intelligence) # Phase 8 product awareness engines import core.engine.sentinel.engines.correlation_engine # noqa: F401 @@ -304,7 +306,6 @@ async def lifespan(app: FastAPI): import core.engine.sentinel.engines.failure_analysis # noqa: F401 import core.engine.sentinel.engines.gap_analyzer # noqa: F401 import core.engine.sentinel.engines.gap_researcher # noqa: F401 - import core.engine.sentinel.engines.github_release_watcher # noqa: F401 # Phase 5b idea + template engines import core.engine.sentinel.engines.idea_incubator # noqa: F401 @@ -332,9 +333,6 @@ async def lifespan(app: FastAPI): # Voice audit sweeper (every 30 minutes) import core.engine.sentinel.engines.voice_audit_sweeper # noqa: F401 - - # S2 whitespace engine - import core.engine.sentinel.engines.whitespace_engine # noqa: F401 from core.engine.api.sentinel import set_scheduler # Start sentinel scheduler diff --git a/core/engine/core/config.py b/core/engine/core/config.py index a17ad64..f4294a2 100644 --- a/core/engine/core/config.py +++ b/core/engine/core/config.py @@ -133,6 +133,13 @@ class Settings(BaseSettings): # Default True — once landed, this should be on for every session. enable_ai_briefing: bool = True + # 0.8 compatibility boundary. The older sentinel engines encode ACE-product + # competitors, community sources, and whitespace scoring directly in the host. + # They remain callable for migration evidence, but are not part of the default + # domain-neutral Intelligence OS runtime. Domains now supply declarative Packs + # and separately authorized connectors instead. + enable_legacy_product_intelligence: bool = False + # In-process TTL for the AI briefing payload (seconds). The briefing # changes slowly (decisions/capabilities updates), so caching reduces # substrate read load. Set to 0 to disable caching. diff --git a/docs/README.md b/docs/README.md index 88f8f33..d8e58fb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -75,6 +75,9 @@ support the public roadmap but do not compete with it for outcome state or dispa 0.8A–0.8F canonical lifecycle, ownership and compatibility map, Atrium and public-resource sequence, World AI Command Center demonstration, Market falsifier, release gates, and stop conditions. +- [Intelligence OS runtime-boundary realignment](design/intelligence-os-runtime-boundary-v0.8.0-work-packet-v1.md) + — the active 0.8B packet, accepted AM4 input, default isolation of embedded product-intelligence + engines, compatibility switch, remaining runtime convergence, and rollback. - [Intelligence Builder Connect work packet](design/intelligence-builder-connect-v0.7.0-work-packet-v1.md) — the bounded 0.7B Connection Agent implementation, reuse audit, failure controls, and evidence plan. diff --git a/docs/design/intelligence-os-runtime-boundary-v0.8.0-work-packet-v1.md b/docs/design/intelligence-os-runtime-boundary-v0.8.0-work-packet-v1.md new file mode 100644 index 0000000..4c50737 --- /dev/null +++ b/docs/design/intelligence-os-runtime-boundary-v0.8.0-work-packet-v1.md @@ -0,0 +1,80 @@ +# ACE 0.8.0 runtime-boundary realignment work packet + +Status: **active 0.8B packet; B1 compatibility isolation implemented** +Public milestone: [issue #40](https://github.com/augmented-cognition-engine/core/issues/40) +Accepted base: `main@bb7f4ba` (0.8A plus explicitly reviewed AM4 lifecycle semantics) + +## Outcome + +The default ACE runtime must compose the domain-neutral Intelligence OS, not the historical +ACE-product intelligence implementation. Compatibility remains deliberate and reversible: + +- `ace.core` owns governed mechanics and has no inward Intelligence dependency; +- `ace.intelligence` owns pure evidence-to-orientation meaning and imports no host; +- `ace.application` composes public Core and Intelligence ports without owning authority or storage; +- Domain Packs remain inert declarative meaning; +- executable source acquisition and external effects remain adapters; and +- the legacy `core.engine` host may bridge into public ACE only through declared compatibility seams. + +This packet does not delete historical code or rewrite immutable evidence. It stops the default +runtime from silently activating a domain-specific branch and makes any temporary opt-in visible. + +## B1 — isolate embedded product intelligence + +The pre-Domain-Pack sentinel host contains four ACE-product engines: + +- community scanning against tracked competitors; +- competitor web/changelog observation; +- competitor GitHub release watching; and +- whitespace scoring over competitor coverage and community pain signals. + +Those implementations remain import-compatible while deployments migrate, but they are no longer +registered by default. `ENABLE_LEGACY_PRODUCT_INTELLIGENCE=true` is the one explicit compatibility +switch. Enabling it emits an operator warning and grants no source, model, or action authority. + +The domain-neutral graph community summarizer remains enabled. World and Market Intelligence must +obtain source-specific behavior from their Domain Packs and authorized connectors rather than this +legacy switch. + +## Accepted AM4 input + +Agent Memory lifecycle and erasure entered 0.8 through a separate compatibility review before this +packet began. Its additive merge onto the 0.8A base passed 157 combined roadmap, package-boundary, +kernel-boundary, exact-eleven, AM1–AM4, and restart-oriented tests. Retention, scoped export/import, +soft forget, and dependency-complete supported-store erasure are therefore available to the later +0.8C resource plane and 0.8D Atrium experience; they are not reimplemented here. + +## Acceptance + +B1 passes only when: + +1. the compatibility flag is false by default and accepts an explicit environment opt-in; +2. the disabled path imports and registers none of the four frozen legacy engines; +3. the opt-in path registers exactly the declared compatibility set; +4. the API composition root contains no unconditional import of those modules; +5. each legacy module remains callable for bounded migration compatibility; +6. Core, Intelligence, application, thin-MCP, naked-kernel, and roadmap boundaries remain green; +7. no Domain Pack, connector, or UI is copied into Core; and +8. the exact eleven-tool public MCP surface is unchanged. + +## Remaining 0.8B work + +B1 does not close 0.8B. The remaining runtime-boundary packet must: + +- publish a machine-checked disposition for the broader product-era `core.engine` surface; +- keep generic planning, authority, execution admission, assurance, outcomes, and erasure behind + Core ports; +- keep Observation-to-Feedback interpretation behind Intelligence and application services; +- verify artifact creation and external effects enter only through explicit strategy/adapter ports; +- prevent new direct callers of deprecated MAKE/SHIP, Living Product Graph category, broad MCP, and + legacy product-intelligence paths; and +- reproduce Connect → Map → Watch → Brief → Activate plus AM3/AM4 behavior after restart. + +0.8C may not depend on an undeclared legacy host route. + +## Rollback + +Reverting B1 restores the old implicit engine registration. No stored record is rewritten by this +change. A deployment that temporarily requires the historical behavior can set the compatibility +flag while its data sources, schedules, and consumers migrate to the Domain Pack lifecycle. The +flag is not a promise that the old engines survive 1.0. diff --git a/tests/test_intelligence_os_runtime_boundary.py b/tests/test_intelligence_os_runtime_boundary.py new file mode 100644 index 0000000..36465e5 --- /dev/null +++ b/tests/test_intelligence_os_runtime_boundary.py @@ -0,0 +1,100 @@ +from __future__ import annotations + +import ast +import os +import subprocess +import sys +from pathlib import Path + +import pytest + +from core.engine.api.legacy_product_intelligence import LEGACY_PRODUCT_INTELLIGENCE_MODULES +from core.engine.core.config import Settings + +pytestmark = pytest.mark.unit + +REPO = Path(__file__).resolve().parents[1] +MAIN = REPO / "core" / "engine" / "api" / "main.py" + + +def _imports(path: Path) -> set[str]: + tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) + names: set[str] = set() + for node in ast.walk(tree): + if isinstance(node, ast.Import): + names.update(alias.name for alias in node.names) + elif isinstance(node, ast.ImportFrom) and node.module: + names.add(node.module) + return names + + +def test_legacy_product_intelligence_is_disabled_by_default_and_explicitly_opt_in(monkeypatch) -> None: + monkeypatch.delenv("ENABLE_LEGACY_PRODUCT_INTELLIGENCE", raising=False) + settings = Settings(jwt_secret="test-secret", _env_file=None) + assert settings.enable_legacy_product_intelligence is False + + monkeypatch.setenv("ENABLE_LEGACY_PRODUCT_INTELLIGENCE", "true") + opted_in = Settings(jwt_secret="test-secret", _env_file=None) + assert opted_in.enable_legacy_product_intelligence is True + + +def test_default_registration_path_loads_no_domain_specific_legacy_engine() -> None: + code = """ +import sys +from core.engine.api.legacy_product_intelligence import ( + LEGACY_PRODUCT_INTELLIGENCE_MODULES, + register_legacy_product_intelligence_engines, +) +loaded = register_legacy_product_intelligence_engines(enabled=False) +unexpected = sorted(name for name in LEGACY_PRODUCT_INTELLIGENCE_MODULES if name in sys.modules) +raise SystemExit(f'unexpected legacy engine imports: {unexpected}' if loaded or unexpected else 0) +""" + environment = { + **os.environ, + "JWT_SECRET": "test-secret", + "ENABLE_LEGACY_PRODUCT_INTELLIGENCE": "false", + } + result = subprocess.run( + [sys.executable, "-c", code], + cwd=REPO, + env=environment, + capture_output=True, + text=True, + check=False, + ) + assert result.returncode == 0, result.stderr or result.stdout + + +def test_explicit_compatibility_opt_in_registers_only_the_frozen_legacy_set() -> None: + code = """ +from core.engine.api.legacy_product_intelligence import ( + LEGACY_PRODUCT_INTELLIGENCE_MODULES, + register_legacy_product_intelligence_engines, +) +from core.engine.sentinel.registry import engine_registry +loaded = register_legacy_product_intelligence_engines(enabled=True) +registered = { + 'community_scanner', + 'competitive_observer', + 'github_release_watcher', + 'whitespace_engine', +} +missing = sorted(registered.difference(engine_registry)) +raise SystemExit(f'compatibility registration failed: loaded={loaded!r}, missing={missing!r}' if loaded != LEGACY_PRODUCT_INTELLIGENCE_MODULES or missing else 0) +""" + environment = {**os.environ, "JWT_SECRET": "test-secret"} + result = subprocess.run( + [sys.executable, "-c", code], + cwd=REPO, + env=environment, + capture_output=True, + text=True, + check=False, + ) + assert result.returncode == 0, result.stderr or result.stdout + + +def test_api_composition_root_has_no_unconditional_legacy_domain_engine_import() -> None: + imports = _imports(MAIN) + assert set(LEGACY_PRODUCT_INTELLIGENCE_MODULES).isdisjoint(imports) + assert "core.engine.api.legacy_product_intelligence" in imports