From e6c989492a62d228ce5f0efc385654561abf4d08 Mon Sep 17 00:00:00 2001 From: Edwin Amirian Date: Thu, 13 Aug 2026 15:06:49 -0700 Subject: [PATCH 1/2] Discover installed Intelligence profiles --- core/engine/api/intelligence_catalog.py | 53 +++++++ core/engine/api/main.py | 2 + .../core/installed_intelligence_catalog.py | 125 +++++++++++++++ .../src/api/intelligenceCatalogApi.test.ts | 27 ++++ .../canvas/src/api/intelligenceCatalogApi.ts | 33 ++++ .../canvas/src/app/atrium/IntelligenceOS.tsx | 10 +- .../src/app/atrium/onboardingModel.test.ts | 13 ++ .../canvas/src/app/atrium/onboardingModel.ts | 9 +- .../atrium/useInstalledIntelligenceCatalog.ts | 28 ++++ tests/test_api_intelligence_catalog.py | 92 +++++++++++ tests/test_installed_intelligence_catalog.py | 148 ++++++++++++++++++ 11 files changed, 538 insertions(+), 2 deletions(-) create mode 100644 core/engine/api/intelligence_catalog.py create mode 100644 core/engine/core/installed_intelligence_catalog.py create mode 100644 core/ui/canvas/src/api/intelligenceCatalogApi.test.ts create mode 100644 core/ui/canvas/src/api/intelligenceCatalogApi.ts create mode 100644 core/ui/canvas/src/app/atrium/useInstalledIntelligenceCatalog.ts create mode 100644 tests/test_api_intelligence_catalog.py create mode 100644 tests/test_installed_intelligence_catalog.py diff --git a/core/engine/api/intelligence_catalog.py b/core/engine/api/intelligence_catalog.py new file mode 100644 index 0000000..7c2fb4e --- /dev/null +++ b/core/engine/api/intelligence_catalog.py @@ -0,0 +1,53 @@ +"""Read-only catalog of validated inert Intelligence starting profiles.""" + +from __future__ import annotations + +from typing import Literal + +from fastapi import APIRouter, Depends +from pydantic import BaseModel, ConfigDict + +from ace.intelligence import IntelligenceOnboardingProfileV1Alpha1 +from core.engine.core.auth import get_current_user +from core.engine.core.installed_intelligence_catalog import discover_installed_onboarding_profiles + +router = APIRouter(prefix="/v1/intelligence/catalog", tags=["intelligence-catalog"]) + + +class InstalledIntelligenceProfileV1(BaseModel): + model_config = ConfigDict(extra="forbid") + + distribution: str + distribution_version: str + resource_path: str + profile: IntelligenceOnboardingProfileV1Alpha1 + + +class InstalledIntelligenceCatalogV1(BaseModel): + model_config = ConfigDict(extra="forbid") + + contract: Literal["ace.http.installed-intelligence-catalog/v1alpha1"] = ( + "ace.http.installed-intelligence-catalog/v1alpha1" + ) + profiles: tuple[InstalledIntelligenceProfileV1, ...] + + +@router.get("/profiles", response_model=InstalledIntelligenceCatalogV1) +async def installed_profiles(user: dict = Depends(get_current_user)) -> InstalledIntelligenceCatalogV1: + """List validated installed profiles; authentication never grants their proposed effects.""" + + del user + return InstalledIntelligenceCatalogV1( + profiles=tuple( + InstalledIntelligenceProfileV1( + distribution=item.distribution, + distribution_version=item.distribution_version, + resource_path=item.resource_path, + profile=item.profile, + ) + for item in discover_installed_onboarding_profiles() + ) + ) + + +__all__ = ["InstalledIntelligenceCatalogV1", "InstalledIntelligenceProfileV1", "installed_profiles", "router"] diff --git a/core/engine/api/main.py b/core/engine/api/main.py index 12a57be..98e7f72 100644 --- a/core/engine/api/main.py +++ b/core/engine/api/main.py @@ -665,6 +665,7 @@ async def api_version(): from core.engine.api.foresight import router as foresight_router from core.engine.api.intel import router as intel_router from core.engine.api.intelligence_builds import router as intelligence_builds_router +from core.engine.api.intelligence_catalog import router as intelligence_catalog_router from core.engine.api.intelligence_resources import router as intelligence_resources_router from core.engine.api.landscape import router as landscape_router from core.engine.api.product_state import router as product_state_router @@ -676,6 +677,7 @@ async def api_version(): app.include_router(capture_router) app.include_router(intel_router) app.include_router(intelligence_builds_router) +app.include_router(intelligence_catalog_router) app.include_router(intelligence_resources_router) app.include_router(landscape_router) app.include_router(product_state_router) diff --git a/core/engine/core/installed_intelligence_catalog.py b/core/engine/core/installed_intelligence_catalog.py new file mode 100644 index 0000000..7a7be06 --- /dev/null +++ b/core/engine/core/installed_intelligence_catalog.py @@ -0,0 +1,125 @@ +"""Discover inert Intelligence onboarding profiles from installed distributions. + +The host recognizes a validated resource shape, never a domain or package name. +Domain distributions remain JSON-only and do not gain executable entry points. +""" + +from __future__ import annotations + +import json +from dataclasses import dataclass +from importlib import metadata +from pathlib import Path, PurePosixPath +from typing import Iterable, Protocol + +from pydantic import ValidationError + +from ace.intelligence import IntelligenceOnboardingProfileV1Alpha1 + +MAX_ONBOARDING_PROFILE_BYTES = 1_000_000 +ONBOARDING_PROFILE_FILENAME = "onboarding_profile.json" + + +class InstalledIntelligenceCatalogError(RuntimeError): + """Installed catalog material was unreadable, invalid, or conflicting.""" + + +class InstalledDistribution(Protocol): + @property + def files(self): ... + + @property + def metadata(self): ... + + @property + def version(self) -> str: ... + + def locate_file(self, path) -> Path: ... + + +@dataclass(frozen=True, slots=True) +class InstalledOnboardingProfile: + distribution: str + distribution_version: str + resource_path: str + profile: IntelligenceOnboardingProfileV1Alpha1 + + +def _distribution_name(distribution: InstalledDistribution) -> str: + value = distribution.metadata.get("Name") + if not isinstance(value, str) or not value.strip(): + raise InstalledIntelligenceCatalogError("installed distribution omitted its canonical name") + return value.strip() + + +def _profile_paths(distribution: InstalledDistribution) -> tuple: + paths = [] + for candidate in distribution.files or (): + normalized = PurePosixPath(str(candidate).replace("\\", "/")) + if ( + normalized.name == ONBOARDING_PROFILE_FILENAME + and "domain_packs" in normalized.parts + and ".dist-info" not in normalized.as_posix() + ): + paths.append(candidate) + return tuple(sorted(paths, key=str)) + + +def _load_profile(*, distribution: InstalledDistribution, candidate, name: str) -> InstalledOnboardingProfile: + resource_path = str(candidate).replace("\\", "/") + try: + path = Path(distribution.locate_file(candidate)) + payload = path.read_bytes() + except OSError as exc: + raise InstalledIntelligenceCatalogError( + f"installed onboarding profile is unreadable: {name}:{resource_path}" + ) from exc + if not payload or len(payload) > MAX_ONBOARDING_PROFILE_BYTES: + raise InstalledIntelligenceCatalogError( + f"installed onboarding profile exceeded its bounded size: {name}:{resource_path}" + ) + try: + json.loads(payload) + profile = IntelligenceOnboardingProfileV1Alpha1.model_validate_json(payload) + except (UnicodeDecodeError, json.JSONDecodeError, ValidationError, TypeError, ValueError) as exc: + raise InstalledIntelligenceCatalogError( + f"installed onboarding profile failed exact validation: {name}:{resource_path}" + ) from exc + return InstalledOnboardingProfile( + distribution=name, + distribution_version=str(distribution.version), + resource_path=resource_path, + profile=profile, + ) + + +def discover_installed_onboarding_profiles( + distributions: Iterable[InstalledDistribution] | None = None, +) -> tuple[InstalledOnboardingProfile, ...]: + """Return validated inert profiles with deterministic provenance and ordering.""" + + installed = metadata.distributions() if distributions is None else distributions + discovered: list[InstalledOnboardingProfile] = [] + for distribution in sorted(installed, key=lambda item: _distribution_name(item).lower()): + name = _distribution_name(distribution) + for candidate in _profile_paths(distribution): + discovered.append(_load_profile(distribution=distribution, candidate=candidate, name=name)) + + by_id: dict[str, InstalledOnboardingProfile] = {} + for item in discovered: + incumbent = by_id.get(item.profile.profile_id) + if incumbent is None: + by_id[item.profile.profile_id] = item + continue + if incumbent.profile.profile_digest != item.profile.profile_digest: + raise InstalledIntelligenceCatalogError( + f"installed onboarding profile identity conflicts across distributions: {item.profile.profile_id}" + ) + return tuple(sorted(by_id.values(), key=lambda item: (item.profile.domain_label or "", item.profile.profile_id))) + + +__all__ = [ + "InstalledIntelligenceCatalogError", + "InstalledOnboardingProfile", + "discover_installed_onboarding_profiles", +] diff --git a/core/ui/canvas/src/api/intelligenceCatalogApi.test.ts b/core/ui/canvas/src/api/intelligenceCatalogApi.test.ts new file mode 100644 index 0000000..1e95729 --- /dev/null +++ b/core/ui/canvas/src/api/intelligenceCatalogApi.test.ts @@ -0,0 +1,27 @@ +import { beforeEach, describe, expect, test, vi } from 'vitest' + +import { getToken } from './auth' +import { queryInstalledIntelligenceCatalog } from './intelligenceCatalogApi' + +vi.mock('./auth', () => ({ clearToken: vi.fn(), getToken: vi.fn() })) + +describe('queryInstalledIntelligenceCatalog', () => { + beforeEach(() => { + vi.mocked(getToken).mockReset() + vi.mocked(getToken).mockResolvedValue('personal-token') + }) + + test('reads installed profiles through the authenticated domain-neutral catalog', async () => { + vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(JSON.stringify({ + contract: 'ace.http.installed-intelligence-catalog/v1alpha1', + profiles: [{ distribution: 'pack', distribution_version: '1.0.0', resource_path: 'domain_packs/x/onboarding_profile.json', profile: {} }], + }), { status: 200, headers: { 'Content-Type': 'application/json' } }))) + + const result = await queryInstalledIntelligenceCatalog() + + expect(result.profiles).toHaveLength(1) + expect(fetch).toHaveBeenCalledWith('/v1/intelligence/catalog/profiles', { + headers: { Authorization: 'Bearer personal-token' }, + }) + }) +}) diff --git a/core/ui/canvas/src/api/intelligenceCatalogApi.ts b/core/ui/canvas/src/api/intelligenceCatalogApi.ts new file mode 100644 index 0000000..ae1f48e --- /dev/null +++ b/core/ui/canvas/src/api/intelligenceCatalogApi.ts @@ -0,0 +1,33 @@ +import { clearToken, getToken } from './auth' + +const BASE = import.meta.env.VITE_API_BASE_URL ?? '' + +export interface InstalledIntelligenceProfile { + readonly distribution: string + readonly distribution_version: string + readonly resource_path: string + readonly profile: unknown +} + +export interface InstalledIntelligenceCatalog { + readonly contract: 'ace.http.installed-intelligence-catalog/v1alpha1' + readonly profiles: readonly InstalledIntelligenceProfile[] +} + +async function getCatalog(token: string): Promise { + return fetch(`${BASE}/v1/intelligence/catalog/profiles`, { + headers: { Authorization: `Bearer ${token}` }, + }) +} + +export async function queryInstalledIntelligenceCatalog(): Promise { + let token = await getToken() + let response = await getCatalog(token) + if (response.status === 401) { + clearToken() + token = await getToken() + response = await getCatalog(token) + } + if (!response.ok) throw new Error(`Installed Intelligence catalog is unavailable (${response.status}).`) + return (await response.json()) as InstalledIntelligenceCatalog +} diff --git a/core/ui/canvas/src/app/atrium/IntelligenceOS.tsx b/core/ui/canvas/src/app/atrium/IntelligenceOS.tsx index 02da756..02d919c 100644 --- a/core/ui/canvas/src/app/atrium/IntelligenceOS.tsx +++ b/core/ui/canvas/src/app/atrium/IntelligenceOS.tsx @@ -42,6 +42,7 @@ import { } from './intelligenceModel' import { ResourceCard } from './ResourceCard' import { useIntelligenceResources } from './useIntelligenceResources' +import { useInstalledIntelligenceCatalog } from './useInstalledIntelligenceCatalog' type Surface = 'intelligence' | 'opportunities' | 'agents' | 'connections' | 'strategy' @@ -422,11 +423,18 @@ export function IntelligenceOS() { const surface = activeSurface(pathname) const copy = SURFACE_COPY[surface] const { page, loading, error, refresh, adoptPage } = useIntelligenceResources() + const installedCatalog = useInstalledIntelligenceCatalog() const groups = useMemo(() => groupResources(page?.items ?? []), [page?.items]) const productName = productDisplayName(page?.product_id) const freshness = pageFreshness(page) const [onboardingOpen, setOnboardingOpen] = useState(false) - const onboardingProfiles = useMemo(() => onboardingProfilesFromResources(page?.items ?? []), [page?.items]) + const onboardingProfiles = useMemo( + () => onboardingProfilesFromResources( + page?.items ?? [], + installedCatalog.map((item) => item.profile), + ), + [installedCatalog, page?.items], + ) const onboardingSession = useMemo(() => onboardingSessionFromResources(page?.items ?? []), [page?.items]) function openFirstBrief() { diff --git a/core/ui/canvas/src/app/atrium/onboardingModel.test.ts b/core/ui/canvas/src/app/atrium/onboardingModel.test.ts index 33822a0..fa2d41c 100644 --- a/core/ui/canvas/src/app/atrium/onboardingModel.test.ts +++ b/core/ui/canvas/src/app/atrium/onboardingModel.test.ts @@ -111,6 +111,19 @@ describe('Atrium onboarding resources', () => { expect(onboardingProfilesFromResources([]).map((item) => item.domain_label)).toEqual(['Custom Intelligence']) }) + it('adds validated installed profiles without replacing admitted product profiles', () => { + const installedWorld = { ...profile, profile_id: 'onboarding_profile:world', domain_label: 'World Intelligence' } + const installedDuplicate = { ...profile, domain_label: 'Installed duplicate' } + expect(onboardingProfilesFromResources( + [resource('builder_profile', profile)], + [installedWorld, installedDuplicate], + ).map((item) => item.domain_label)).toEqual([ + 'Test domain', + 'World Intelligence', + 'Custom Intelligence', + ]) + }) + it('uses the latest exact builder session revision', () => { const older = resource('builder_session', { ...session, sequence: 6, stage: 'intelligence_model_approved' }, 6) const latest = resource('builder_session', session, 7) diff --git a/core/ui/canvas/src/app/atrium/onboardingModel.ts b/core/ui/canvas/src/app/atrium/onboardingModel.ts index 620f3ab..d1c17e4 100644 --- a/core/ui/canvas/src/app/atrium/onboardingModel.ts +++ b/core/ui/canvas/src/app/atrium/onboardingModel.ts @@ -273,7 +273,10 @@ export function parseOnboardingProfile(value: unknown): IntelligenceOnboardingPr * generic discovery agents rather than naming a domain. Exact duplicates are * collapsed by profile identity; no domain name is hard-coded here. */ -export function onboardingProfilesFromResources(items: readonly IntelligenceResourceRecord[]): readonly IntelligenceOnboardingProfile[] { +export function onboardingProfilesFromResources( + items: readonly IntelligenceResourceRecord[], + installedProfiles: readonly unknown[] = [], +): readonly IntelligenceOnboardingProfile[] { const profiles = new Map() for (const item of items) { const payload = canonicalPayload(item.payload) @@ -286,6 +289,10 @@ export function onboardingProfilesFromResources(items: readonly IntelligenceReso if (profile !== null && !profiles.has(profile.profile_id)) profiles.set(profile.profile_id, profile) } } + for (const value of installedProfiles) { + const profile = parseOnboardingProfile(value) + if (profile !== null && !profiles.has(profile.profile_id)) profiles.set(profile.profile_id, profile) + } profiles.set(FALLBACK_PROFILE.profile_id, FALLBACK_PROFILE) return [...profiles.values()] } diff --git a/core/ui/canvas/src/app/atrium/useInstalledIntelligenceCatalog.ts b/core/ui/canvas/src/app/atrium/useInstalledIntelligenceCatalog.ts new file mode 100644 index 0000000..f1b5285 --- /dev/null +++ b/core/ui/canvas/src/app/atrium/useInstalledIntelligenceCatalog.ts @@ -0,0 +1,28 @@ +import { useEffect, useState } from 'react' + +import { + queryInstalledIntelligenceCatalog, + type InstalledIntelligenceProfile, +} from '@/api/intelligenceCatalogApi' + +export function useInstalledIntelligenceCatalog(): readonly InstalledIntelligenceProfile[] { + const [profiles, setProfiles] = useState([]) + + useEffect(() => { + let cancelled = false + queryInstalledIntelligenceCatalog() + .then((catalog) => { + if (!cancelled) setProfiles(catalog.profiles) + }) + .catch(() => { + // Installed profiles enhance first use. Current admitted product + // profiles and Core's Custom starting point remain usable if the + // local catalog cannot be read. + }) + return () => { + cancelled = true + } + }, []) + + return profiles +} diff --git a/tests/test_api_intelligence_catalog.py b/tests/test_api_intelligence_catalog.py new file mode 100644 index 0000000..8228969 --- /dev/null +++ b/tests/test_api_intelligence_catalog.py @@ -0,0 +1,92 @@ +from __future__ import annotations + +from datetime import UTC, datetime, timedelta + +import pytest +from fastapi import FastAPI +from httpx import ASGITransport, AsyncClient + +from ace.intelligence import IntelligenceOnboardingProfileV1Alpha1 +from core.engine.api import intelligence_catalog +from core.engine.core.auth import get_current_user +from core.engine.core.installed_intelligence_catalog import InstalledOnboardingProfile + +pytestmark = pytest.mark.unit + + +def _profile() -> IntelligenceOnboardingProfileV1Alpha1: + return IntelligenceOnboardingProfileV1Alpha1.model_validate_json( + """{ + "contract": "ace.intelligence.onboarding-profile/v1alpha1", + "profile_id": "intelligence_onboarding_profile:world-ai", + "topic_id": "artificial_intelligence", + "domain_label": "World Intelligence", + "topic_label": "Artificial intelligence", + "display_name": "AI Command Center", + "prompt": "What do you need to stay ahead of?", + "description": "A cited view of material AI change for one person.", + "starter_prompts": ["Keep me ahead of meaningful changes in AI."], + "outcomes": [{ + "outcome_id": "decision_readiness", + "label": "Stay decision-ready", + "description": "Orient around material change and evidence.", + "icon_hint": "strategy", + "recommended_watch_ids": [], + "recommended_intelligence_ids": [], + "recommended_topic_labels": [], + "recommended_intelligence_labels": [] + }], + "source_groups": [], + "cadences": [{"cadence_id": "daily", "label": "Daily", "description": "A daily orientation."}], + "default_cadence_id": "daily", + "first_value": {"public_sources_first": true, "private_sources_optional": true, "completion_label": "Open Brief"}, + "guardrails": { + "declarative_only": true, + "authorizes_connections": false, + "authorizes_monitors": false, + "proposed_sources_are_not_connected": true, + "feedback_may_reweight_relevance_not_authority": true + } + }""" + ) + + +@pytest.mark.asyncio +async def test_installed_catalog_exposes_validated_inert_profiles_with_provenance(monkeypatch) -> None: + monkeypatch.setattr( + intelligence_catalog, + "discover_installed_onboarding_profiles", + lambda: ( + InstalledOnboardingProfile( + distribution="ace-domain-world-intelligence", + distribution_version="1.0.0", + resource_path="domain_packs/world_intelligence_ai/onboarding_profile.json", + profile=_profile(), + ), + ), + ) + app = FastAPI() + app.include_router(intelligence_catalog.router) + app.dependency_overrides[get_current_user] = lambda: { + "sub": "principal:personal-analyst", + "product": "product:personal-intelligence", + "exp": (datetime.now(UTC) + timedelta(hours=1)).timestamp(), + } + + async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client: + response = await client.get("/v1/intelligence/catalog/profiles") + + assert response.status_code == 200 + body = response.json() + assert body["contract"] == "ace.http.installed-intelligence-catalog/v1alpha1" + assert body["profiles"][0]["distribution"] == "ace-domain-world-intelligence" + assert body["profiles"][0]["profile"]["domain_label"] == "World Intelligence" + assert body["profiles"][0]["profile"]["profile_digest"].startswith("sha256:") + + +def test_installed_catalog_openapi_exposes_stable_response_contract() -> None: + app = FastAPI() + app.include_router(intelligence_catalog.router) + operation = app.openapi()["paths"]["/v1/intelligence/catalog/profiles"]["get"] + response_schema = operation["responses"]["200"]["content"]["application/json"]["schema"] + assert response_schema["$ref"].endswith("InstalledIntelligenceCatalogV1") diff --git a/tests/test_installed_intelligence_catalog.py b/tests/test_installed_intelligence_catalog.py new file mode 100644 index 0000000..0a38130 --- /dev/null +++ b/tests/test_installed_intelligence_catalog.py @@ -0,0 +1,148 @@ +from __future__ import annotations + +import json +from dataclasses import dataclass +from pathlib import Path, PurePosixPath + +import pytest + +from core.engine.core.installed_intelligence_catalog import ( + InstalledIntelligenceCatalogError, + discover_installed_onboarding_profiles, +) + +pytestmark = pytest.mark.unit + + +def _profile(*, profile_id: str, topic_id: str, domain_label: str) -> dict: + return { + "contract": "ace.intelligence.onboarding-profile/v1alpha1", + "profile_id": profile_id, + "topic_id": topic_id, + "domain_label": domain_label, + "topic_label": topic_id.replace("_", " ").title(), + "display_name": f"{domain_label} Command Center", + "prompt": "What do you need to stay ahead of?", + "description": "A validated inert starting point for one personal Intelligence picture.", + "starter_prompts": ["Keep me ahead of material changes that affect my decisions."], + "outcomes": [ + { + "outcome_id": "decision_readiness", + "label": "Stay decision-ready", + "description": "Orient around meaningful change and the evidence behind it.", + "icon_hint": "strategy", + "recommended_watch_ids": [], + "recommended_intelligence_ids": [], + "recommended_topic_labels": [], + "recommended_intelligence_labels": [], + } + ], + "source_groups": [], + "cadences": [{"cadence_id": "daily", "label": "Daily", "description": "A concise daily orientation."}], + "default_cadence_id": "daily", + "first_value": { + "public_sources_first": True, + "private_sources_optional": True, + "completion_label": "Open my first Brief", + }, + "guardrails": { + "declarative_only": True, + "authorizes_connections": False, + "authorizes_monitors": False, + "proposed_sources_are_not_connected": True, + "feedback_may_reweight_relevance_not_authority": True, + }, + } + + +@dataclass +class _Metadata: + name: str + + def get(self, key: str): + return self.name if key == "Name" else None + + +class _Distribution: + def __init__(self, *, root: Path, name: str, version: str, resources: dict[str, dict | str]) -> None: + self.root = root + self.metadata = _Metadata(name) + self.version = version + self.files = tuple(PurePosixPath(path) for path in resources) + for relative, document in resources.items(): + path = root / relative + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(document) if isinstance(document, dict) else document) + + def locate_file(self, path) -> Path: + return self.root / str(path) + + +def test_discovers_world_and_market_profiles_by_validated_shape_not_package_name(tmp_path: Path) -> None: + market = _Distribution( + root=tmp_path / "market", + name="anything-market", + version="1.0.0", + resources={ + "domain_packs/market_intelligence/onboarding_profile.json": _profile( + profile_id="intelligence_onboarding_profile:market", + topic_id="market_intelligence", + domain_label="Market Intelligence", + ) + }, + ) + world = _Distribution( + root=tmp_path / "world", + name="anything-world", + version="1.0.0", + resources={ + "domain_packs/world_intelligence_ai/onboarding_profile.json": _profile( + profile_id="intelligence_onboarding_profile:world", + topic_id="artificial_intelligence", + domain_label="World Intelligence", + ), + "domain_packs/world_intelligence_ai/manifest.json": {}, + }, + ) + + found = discover_installed_onboarding_profiles([world, market]) + + assert [item.profile.domain_label for item in found] == ["Market Intelligence", "World Intelligence"] + assert [item.distribution for item in found] == ["anything-market", "anything-world"] + assert all(item.profile.profile_digest.startswith("sha256:") for item in found) + + +def test_rejects_malformed_or_conflicting_installed_profiles(tmp_path: Path) -> None: + malformed = _Distribution( + root=tmp_path / "malformed", + name="malformed-pack", + version="1.0.0", + resources={"domain_packs/broken/onboarding_profile.json": "not-json"}, + ) + with pytest.raises(InstalledIntelligenceCatalogError, match="failed exact validation"): + discover_installed_onboarding_profiles([malformed]) + + first = _profile( + profile_id="intelligence_onboarding_profile:shared", + topic_id="first_topic", + domain_label="First Intelligence", + ) + second = _profile( + profile_id="intelligence_onboarding_profile:shared", + topic_id="second_topic", + domain_label="Second Intelligence", + ) + one = _Distribution( + root=tmp_path / "one", + name="pack-one", + version="1.0.0", + resources={"domain_packs/one/onboarding_profile.json": first}, + ) + two = _Distribution( + root=tmp_path / "two", + name="pack-two", + version="1.0.0", + resources={"domain_packs/two/onboarding_profile.json": second}, + ) + with pytest.raises(InstalledIntelligenceCatalogError, match="identity conflicts"): + discover_installed_onboarding_profiles([one, two]) From 7f237ebbe29d6d96856768dec0b6455a318307a0 Mon Sep 17 00:00:00 2001 From: Edwin Amirian Date: Thu, 13 Aug 2026 15:30:00 -0700 Subject: [PATCH 2/2] Preserve the installed catalog boundary --- ace/application/__init__.py | 2 ++ ace/application/intelligence_builder_resource_projection.py | 1 + core/engine/api/intelligence_catalog.py | 6 ++++-- core/engine/core/installed_intelligence_catalog.py | 3 ++- tests/test_public_core_boundaries.py | 1 + 5 files changed, 10 insertions(+), 3 deletions(-) diff --git a/ace/application/__init__.py b/ace/application/__init__.py index a90247b..1953563 100644 --- a/ace/application/__init__.py +++ b/ace/application/__init__.py @@ -335,6 +335,7 @@ IntelligenceBuilderPresentationService, IntelligenceBuilderResourceProjectionReader, IntelligenceOnboardingProfileAdmission, + IntelligenceOnboardingProfileV1Alpha1, ) from ace.application.intelligence_ledger import ( PreparedIntelligenceAdmission, @@ -482,6 +483,7 @@ "INTELLIGENCE_ONBOARDING_PROFILE_RECORD_KIND", "IntelligenceBuilderPresentationService", "IntelligenceBuilderResourceProjectionReader", + "IntelligenceOnboardingProfileV1Alpha1", "IntelligenceOnboardingProfileAdmission", "ActionResourceProjectionReader", "RESOURCE_QUERY_AUTHORITY", diff --git a/ace/application/intelligence_builder_resource_projection.py b/ace/application/intelligence_builder_resource_projection.py index 44841d1..5bc9937 100644 --- a/ace/application/intelligence_builder_resource_projection.py +++ b/ace/application/intelligence_builder_resource_projection.py @@ -398,5 +398,6 @@ async def read( "INTELLIGENCE_ONBOARDING_PROFILE_RECORD_KIND", "IntelligenceBuilderPresentationService", "IntelligenceBuilderResourceProjectionReader", + "IntelligenceOnboardingProfileV1Alpha1", "IntelligenceOnboardingProfileAdmission", ] diff --git a/core/engine/api/intelligence_catalog.py b/core/engine/api/intelligence_catalog.py index 7c2fb4e..90e3703 100644 --- a/core/engine/api/intelligence_catalog.py +++ b/core/engine/api/intelligence_catalog.py @@ -7,9 +7,11 @@ from fastapi import APIRouter, Depends from pydantic import BaseModel, ConfigDict -from ace.intelligence import IntelligenceOnboardingProfileV1Alpha1 from core.engine.core.auth import get_current_user -from core.engine.core.installed_intelligence_catalog import discover_installed_onboarding_profiles +from core.engine.core.installed_intelligence_catalog import ( + IntelligenceOnboardingProfileV1Alpha1, + discover_installed_onboarding_profiles, +) router = APIRouter(prefix="/v1/intelligence/catalog", tags=["intelligence-catalog"]) diff --git a/core/engine/core/installed_intelligence_catalog.py b/core/engine/core/installed_intelligence_catalog.py index 7a7be06..ab79bba 100644 --- a/core/engine/core/installed_intelligence_catalog.py +++ b/core/engine/core/installed_intelligence_catalog.py @@ -14,7 +14,7 @@ from pydantic import ValidationError -from ace.intelligence import IntelligenceOnboardingProfileV1Alpha1 +from ace.application import IntelligenceOnboardingProfileV1Alpha1 MAX_ONBOARDING_PROFILE_BYTES = 1_000_000 ONBOARDING_PROFILE_FILENAME = "onboarding_profile.json" @@ -121,5 +121,6 @@ def discover_installed_onboarding_profiles( __all__ = [ "InstalledIntelligenceCatalogError", "InstalledOnboardingProfile", + "IntelligenceOnboardingProfileV1Alpha1", "discover_installed_onboarding_profiles", ] diff --git a/tests/test_public_core_boundaries.py b/tests/test_public_core_boundaries.py index a926a8c..56bf801 100644 --- a/tests/test_public_core_boundaries.py +++ b/tests/test_public_core_boundaries.py @@ -108,6 +108,7 @@ def test_host_adapters_are_the_only_core_engine_edge_into_public_ace() -> None: "core/engine/core/immutable_records.py", "core/engine/core/intelligence_resource_plane.py", "core/engine/core/intelligence_build.py", + "core/engine/core/installed_intelligence_catalog.py", "core/engine/core/live_cognition.py", "core/engine/core/action_execution.py", "core/engine/core/agent_composition_runtime.py",