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
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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://<lan-ip>:5173,http://<vpn-ip>: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
Expand Down
46 changes: 46 additions & 0 deletions core/engine/api/legacy_product_intelligence.py
Original file line number Diff line number Diff line change
@@ -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",
]
16 changes: 7 additions & 9 deletions core/engine/api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions core/engine/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
100 changes: 100 additions & 0 deletions tests/test_intelligence_os_runtime_boundary.py
Original file line number Diff line number Diff line change
@@ -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