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
8 changes: 8 additions & 0 deletions ace/application/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,11 +334,15 @@
from ace.application.intelligence_resource_plane import (
RESOURCE_QUERY_AUTHORITY,
RESOURCE_QUERY_OPERATION,
IntelligenceResourceCursorV1Alpha1,
IntelligenceResourceKind,
IntelligenceResourcePageV1Alpha1,
IntelligenceResourcePlaneAuthorizationPort,
IntelligenceResourcePlaneError,
IntelligenceResourcePlaneService,
IntelligenceResourceProjectionBatch,
IntelligenceResourceProjectionReader,
IntelligenceResourceQueryV1Alpha1,
)
from ace.application.intelligence_resource_projection import (
IntelligenceLedgerProjectionError,
Expand Down Expand Up @@ -441,11 +445,15 @@
__all__ = [
"RESOURCE_QUERY_AUTHORITY",
"RESOURCE_QUERY_OPERATION",
"IntelligenceResourceCursorV1Alpha1",
"IntelligenceResourceKind",
"IntelligenceResourcePageV1Alpha1",
"IntelligenceResourcePlaneAuthorizationPort",
"IntelligenceResourcePlaneError",
"IntelligenceResourcePlaneService",
"IntelligenceResourceProjectionBatch",
"IntelligenceResourceProjectionReader",
"IntelligenceResourceQueryV1Alpha1",
"IntelligenceLedgerProjectionError",
"IntelligenceLedgerResourceProjectionReader",
"ASSERTION_DECISION_RECORD_KIND",
Expand Down
5 changes: 5 additions & 0 deletions ace/application/intelligence_resource_plane.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from ace.core.runtime_use import AuthorityUseReceiptV1Alpha1
from ace.intelligence.contracts.resource_plane import (
IntelligenceResourceCursorV1Alpha1,
IntelligenceResourceKind,
IntelligenceResourcePageState,
IntelligenceResourcePageV1Alpha1,
IntelligenceResourceQueryV1Alpha1,
Expand Down Expand Up @@ -217,6 +218,10 @@ async def query(
"IntelligenceResourcePlaneAuthorizationPort",
"IntelligenceResourcePlaneError",
"IntelligenceResourcePlaneService",
"IntelligenceResourceCursorV1Alpha1",
"IntelligenceResourceKind",
"IntelligenceResourcePageV1Alpha1",
"IntelligenceResourceQueryV1Alpha1",
"IntelligenceResourceProjectionBatch",
"IntelligenceResourceProjectionReader",
]
10 changes: 5 additions & 5 deletions ace/intelligence/contracts/resource_plane.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,11 +307,11 @@ def validate_scope_time_and_identity(self) -> Self:
raise ValueError("resource query crossed authenticated product scope")
if self.available_at < self.as_of:
raise ValueError("query available_at cannot precede as_of")
if not (
self.authenticated_context.authenticated_at <= self.available_at < self.authenticated_context.expires_at
):
raise ValueError("query availability cutoff must be inside the authenticated window")
material = self.model_dump(mode="json", exclude={"cursor", "query_id", "query_digest"})
material = self.model_dump(
mode="json",
exclude={"authenticated_context", "cursor", "query_id", "query_digest"},
)
material["actor_ref"] = self.authenticated_context.actor_ref
digest = canonical_hash(material)
expected_id = f"resource_query:{digest[:32]}"
expected_digest = f"sha256:{digest}"
Expand Down
61 changes: 61 additions & 0 deletions core/engine/api/intelligence_resources.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""HTTP transport for the governed ACE Intelligence resource plane."""

from __future__ import annotations

from fastapi import APIRouter, Depends, HTTPException, status

from core.engine.core.auth import get_current_user
from core.engine.core.intelligence_resource_plane import (
IntelligenceResourceHttpContractConflict,
IntelligenceResourceHttpDenied,
IntelligenceResourceHttpQueryV1,
IntelligenceResourceHttpRuntime,
IntelligenceResourceHttpUnauthenticated,
IntelligenceResourceHttpUnavailable,
IntelligenceResourcePageV1Alpha1,
intelligence_resource_runtime,
query_intelligence_resource_page,
)

router = APIRouter(prefix="/v1/intelligence/resources", tags=["intelligence-resources"])


@router.post("/query", response_model=IntelligenceResourcePageV1Alpha1)
async def query_intelligence_resources(
selector: IntelligenceResourceHttpQueryV1,
user: dict = Depends(get_current_user),
runtime: IntelligenceResourceHttpRuntime = Depends(intelligence_resource_runtime),
) -> IntelligenceResourcePageV1Alpha1:
"""Reauthenticate and reauthorize one point-in-time page; cursors grant no authority."""

try:
return await query_intelligence_resource_page(
selector=selector,
user=user,
runtime=runtime,
)
except IntelligenceResourceHttpUnauthenticated as exc:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail="Verified token lacks product scope"
) from exc
except IntelligenceResourceHttpDenied as exc:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Intelligence query denied") from exc
except IntelligenceResourceHttpUnavailable as exc:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Intelligence authentication evidence is unavailable",
) from exc
except IntelligenceResourceHttpContractConflict as exc:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Intelligence resource query could not preserve its exact contract",
) from exc


__all__ = [
"IntelligenceResourceHttpQueryV1",
"IntelligenceResourceHttpRuntime",
"intelligence_resource_runtime",
"query_intelligence_resources",
"router",
]
2 changes: 2 additions & 0 deletions core/engine/api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,7 @@ async def api_version():
from core.engine.api.extension_invocations import router as extension_invocations_router
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_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
from core.engine.api.sentinels import router as sentinels_router
Expand All @@ -673,6 +674,7 @@ async def api_version():
app.include_router(extension_invocations_router)
app.include_router(capture_router)
app.include_router(intel_router)
app.include_router(intelligence_resources_router)
app.include_router(landscape_router)
app.include_router(product_state_router)
app.include_router(foresight_router)
Expand Down
143 changes: 143 additions & 0 deletions core/engine/core/intelligence_resource_plane.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
"""Supported host composition for the governed Intelligence resource plane.

This is the only legacy Core host edge into the public ACE Application layer.
The HTTP adapter depends on this host boundary and never imports public ACE
packages directly.
"""

from __future__ import annotations

from dataclasses import dataclass
from datetime import UTC, datetime

from pydantic import BaseModel, ConfigDict, Field

from ace.application import (
RESOURCE_QUERY_AUTHORITY,
IntelligenceLedgerResourceProjectionReader,
IntelligenceResourceCursorV1Alpha1,
IntelligenceResourceKind,
IntelligenceResourcePageV1Alpha1,
IntelligenceResourcePlaneAuthorizationPort,
IntelligenceResourcePlaneError,
IntelligenceResourcePlaneService,
IntelligenceResourceQueryV1Alpha1,
)
from ace.core import ImmutableRecordPersistenceError, ImmutableRecordStore
from core.engine.core.agent_composition_runtime import (
GovernedCompositionAuthorityError,
GovernedStateRuntimeUseResolver,
persist_task_authentication_receipt,
)
from core.engine.core.db import pool
from core.engine.core.governed_state import SurrealGovernedStateStore
from core.engine.core.immutable_records import SurrealImmutableRecordStore


class IntelligenceResourceHttpQueryV1(BaseModel):
"""HTTP selector; authenticated context comes only from verified claims."""

model_config = ConfigDict(extra="forbid")

authority_grant_ref: str = Field(min_length=1, max_length=240)
resource_kinds: tuple[IntelligenceResourceKind, ...] = Field(min_length=1, max_length=32)
subject_refs: tuple[str, ...] = Field(default_factory=tuple, max_length=256)
as_of: datetime
available_at: datetime
page_size: int = Field(ge=1, le=200)
cursor: IntelligenceResourceCursorV1Alpha1 | None = None


@dataclass(frozen=True, slots=True)
class IntelligenceResourceHttpRuntime:
records: ImmutableRecordStore
authority: IntelligenceResourcePlaneAuthorizationPort


class IntelligenceResourceHttpDenied(RuntimeError):
"""The verified principal or current Core grant denied the query."""


class IntelligenceResourceHttpUnauthenticated(RuntimeError):
"""The verified token did not contain a usable product-scoped principal."""


class IntelligenceResourceHttpUnavailable(RuntimeError):
"""Required authentication evidence could not be persisted."""


class IntelligenceResourceHttpContractConflict(RuntimeError):
"""The query could not preserve the exact resource-plane contract."""


def intelligence_resource_runtime() -> IntelligenceResourceHttpRuntime:
records = SurrealImmutableRecordStore(pool)
governed_state = SurrealGovernedStateStore(pool)
return IntelligenceResourceHttpRuntime(
records=records,
authority=GovernedStateRuntimeUseResolver(governed_state=governed_state),
)


def _verified_claims(user: dict) -> tuple[str, str]:
actor_ref = user.get("sub")
product_id = user.get("product")
authorities = user.get("authorities")
if not isinstance(actor_ref, str) or not actor_ref or not isinstance(product_id, str) or not product_id:
raise IntelligenceResourceHttpUnauthenticated("verified token lacks product scope")
if not isinstance(authorities, list) or RESOURCE_QUERY_AUTHORITY not in authorities:
raise IntelligenceResourceHttpDenied("intelligence read authority is required")
return actor_ref, product_id


async def query_intelligence_resource_page(
*,
selector: IntelligenceResourceHttpQueryV1,
user: dict,
runtime: IntelligenceResourceHttpRuntime,
) -> IntelligenceResourcePageV1Alpha1:
"""Bind verified host context to one authorized public resource query."""

actor_ref, product_id = _verified_claims(user)
evaluated_at = datetime.now(UTC)
try:
authentication = await persist_task_authentication_receipt(
claims={**user, "sub": actor_ref, "product": product_id},
verified_at=evaluated_at,
store=runtime.records,
verification_policy_ref="jwt_verification_policy:v1",
)
request = IntelligenceResourceQueryV1Alpha1(
authenticated_context=authentication.runtime_context(),
product_id=product_id,
authority_grant_ref=selector.authority_grant_ref,
resource_kinds=selector.resource_kinds,
subject_refs=selector.subject_refs,
as_of=selector.as_of,
available_at=selector.available_at,
page_size=selector.page_size,
cursor=selector.cursor,
)
return await IntelligenceResourcePlaneService(
reader=IntelligenceLedgerResourceProjectionReader(store=runtime.records),
authority=runtime.authority,
).query(request, evaluated_at=evaluated_at)
except GovernedCompositionAuthorityError as exc:
raise IntelligenceResourceHttpDenied("current Core grant denied the query") from exc
except ImmutableRecordPersistenceError as exc:
raise IntelligenceResourceHttpUnavailable("authentication evidence is unavailable") from exc
except (IntelligenceResourcePlaneError, TypeError, ValueError) as exc:
raise IntelligenceResourceHttpContractConflict("resource query contract could not be preserved") from exc


__all__ = [
"IntelligenceResourceHttpContractConflict",
"IntelligenceResourceHttpDenied",
"IntelligenceResourceHttpQueryV1",
"IntelligenceResourceHttpRuntime",
"IntelligenceResourceHttpUnauthenticated",
"IntelligenceResourceHttpUnavailable",
"IntelligenceResourcePageV1Alpha1",
"intelligence_resource_runtime",
"query_intelligence_resource_page",
]
17 changes: 12 additions & 5 deletions docs/design/intelligence-resource-plane-v0.8.0-work-packet-v1.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# ACE 0.8.0 unified Intelligence resource plane work packet

Status: **active 0.8C packet; C1 public facade and C2 immutable-ledger projection implemented**
Status: **active 0.8C packet; C1 facade, C2 ledger projection, and C3 governed HTTP query implemented**
Public milestone: [issue #40](https://github.com/augmented-cognition-engine/core/issues/40)
Accepted base: `main@bf4a75a` (0.8A architecture, AM4 lifecycle, completed 0.8B, and C1 facade)
Accepted base: `main@6b4d6b2` (0.8A architecture, AM4 lifecycle, completed 0.8B, C1 facade, and C2 projection)

## Outcome

Expand Down Expand Up @@ -73,9 +73,16 @@ projects Entity Snapshots as public Entities, preserves exact lineage and payloa
subject and cursor, and remains reproducible when reconstructed over the same store. Unsupported or
unavailable buckets return explicit degradation while preserving available truth.

C3 must add governed-state projections for the remaining canonical families, expose the contracts
through one supported machine interface, and verify packaged schema/import integrity. C4 must prove
Atrium consumes that interface rather than privileged internal state.
C3 exposes `POST /v1/intelligence/resources/query` as the first machine interface. The host derives
the authenticated context from a verified bearer token, persists an opaque authentication receipt,
requires the token's `observe_read` authority, resolves the current Core grant, and returns the same
public page contract. Historical data cutoffs are independent from login time; every page is
reauthenticated and reauthorized, while query identity remains stable across authentication receipt
refreshes for the same actor and exact selector.

C3 must still add governed-state projections for the remaining canonical families and complete
packaged schema/import integrity. C4 must prove Atrium consumes this interface rather than
privileged internal state.

The 0.8C exit gate is one authorized query path that can reproduce the evidence-to-outcome resource
chain after restart, report partial truth honestly, and remain identical for World and Market
Expand Down
58 changes: 54 additions & 4 deletions tests/intelligence/test_intelligence_resource_plane.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,19 @@
NOW = datetime(2026, 8, 12, 20, 0, tzinfo=UTC)


def _context(*, product_id: str = PRODUCT) -> AuthenticatedRuntimeContextV1Alpha1:
def _context(
*,
product_id: str = PRODUCT,
actor_ref: str = "principal:analyst",
receipt_suffix: str = "resource-plane",
authenticated_at: datetime = NOW - timedelta(minutes=10),
) -> AuthenticatedRuntimeContextV1Alpha1:
return AuthenticatedRuntimeContextV1Alpha1(
product_id=product_id,
actor_ref="principal:analyst",
authentication_receipt_ref="authentication_receipt:resource-plane",
actor_ref=actor_ref,
authentication_receipt_ref=f"authentication_receipt:{receipt_suffix}",
authentication_receipt_digest="sha256:" + "a" * 64,
authenticated_at=NOW - timedelta(minutes=10),
authenticated_at=authenticated_at,
expires_at=NOW + timedelta(minutes=30),
)

Expand Down Expand Up @@ -215,6 +221,50 @@ def test_query_identity_excludes_cursor_but_cursor_is_bound_to_the_exact_query()
)


def test_query_identity_survives_reauthentication_but_remains_actor_bound() -> None:
first = _query()
refreshed_context = _context(
receipt_suffix="resource-plane-refresh",
authenticated_at=NOW - timedelta(minutes=1),
)
refreshed = IntelligenceResourceQueryV1Alpha1(
**{
**first.model_dump(
mode="python",
exclude={"authenticated_context", "query_id", "query_digest"},
),
"authenticated_context": refreshed_context,
}
)
assert refreshed.query_id == first.query_id
assert refreshed.query_digest == first.query_digest

other_actor = IntelligenceResourceQueryV1Alpha1(
**{
**first.model_dump(
mode="python",
exclude={"authenticated_context", "query_id", "query_digest"},
),
"authenticated_context": _context(actor_ref="principal:other"),
}
)
assert other_actor.query_id != first.query_id


def test_historical_cutoff_is_authorized_at_request_time_not_authentication_time() -> None:
context = _context(authenticated_at=NOW)
query = IntelligenceResourceQueryV1Alpha1(
authenticated_context=context,
product_id=PRODUCT,
authority_grant_ref="authority_grant:resource-read",
resource_kinds=(IntelligenceResourceKind.BRIEF,),
as_of=NOW - timedelta(days=2),
available_at=NOW - timedelta(days=1),
page_size=10,
)
assert query.available_at < context.authenticated_at


def test_record_requires_exact_provenance_scope_and_revision_lineage() -> None:
prior = _reference(IntelligenceResourceKind.ENTITY, "a", revision=1)
current = _reference(IntelligenceResourceKind.ENTITY, "a", revision=2, available_at=NOW + timedelta(minutes=1))
Expand Down
Loading