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
33 changes: 20 additions & 13 deletions Cortex_Preview.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@
from cortex_backend.api import BackendDependencies, create_app # noqa: E402
from cortex_backend.api.security import SessionManager # noqa: E402
from cortex_backend.core.paths import AppPaths # noqa: E402
from cortex_backend.execution.coordinator import DurableFakeCoordinator # noqa: E402
from cortex_backend.execution.lifecycle import ExecutionLifecycle, RuntimeHealth # noqa: E402
from cortex_backend.execution.lifecycle import ExecutionLifecycle # noqa: E402
from cortex_backend.execution.qualification import ( # noqa: E402
QualificationLifecycleConfig,
build_execution_lifecycle,
)
from cortex_backend.execution.repository import ExecutionRepository # noqa: E402
from cortex_backend.repositories.chats import LegacyDatabaseChatRepository # noqa: E402
from cortex_backend.repositories.legacy_settings import LegacySettingsReader # noqa: E402
Expand All @@ -39,24 +42,28 @@ def build_preview_app(
frontend_dist: Path | None = None,
serve_frontend: bool = True,
handoff_secret: str | None = None,
execution_profile: str | None = None,
qualification: QualificationLifecycleConfig | None = None,
execution_lifecycle: ExecutionLifecycle | None = None,
):
"""Build the local web application without starting a server."""
paths = AppPaths.from_data_dir(data_dir) if data_dir else AppPaths.for_current_user()
execution_repository = ExecutionRepository(
paths.execution_database,
paths.execution_artifacts,
)
execution_lifecycle = ExecutionLifecycle(
execution_repository,
coordinator_factory=lambda repository: DurableFakeCoordinator(
repository, auto_recover=False
),
health_check=lambda: RuntimeHealth.blocked(
code="runtime_unavailable",
message="A qualified execution runtime is not enabled in this build.",
),
enabled=False,
)
if execution_lifecycle is not None and (
execution_profile is not None or qualification is not None
):
raise ValueError(
"execution lifecycle cannot be combined with an execution profile"
)
if execution_lifecycle is None:
execution_lifecycle = build_execution_lifecycle(
execution_repository,
profile=execution_profile,
qualification=qualification,
)
database = DatabaseManager(app_paths=paths)
database.migrate_from_json_if_needed()
permanent_memory = PermanentMemoryManager(app_paths=paths)
Expand Down
25 changes: 21 additions & 4 deletions backend/cortex_backend/execution/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"""Durable execution primitives and provider-independent safety contracts.

Only deterministic contracts, the storage-only signed bundle installer, and the
reviewed broker transports are exposed in this phase. Native transport and bundle
storage never load a provider; real runtime providers stay absent until later ADR
gates are approved.
Only deterministic contracts, the storage-only signed bundle installer, the
reviewed broker transports, and the explicit local qualification composition are
exposed in this phase. Native transport and bundle storage never load a provider;
the normal application remains default-off and a recipe request coordinator is a
later ADR slice.
"""

from .broker import (
Expand Down Expand Up @@ -43,6 +44,15 @@
verify_keyring_update,
)
from .lifecycle import ExecutionLifecycle, LifecycleSnapshot, RuntimeHealth
from .qualification import (
CoordinatorFactory,
ExecutionProfile,
ProviderHealthProbe,
QualificationLifecycleConfig,
QualificationProfileError,
build_execution_lifecycle,
parse_execution_profile,
)
from .manifest import (
ManifestEntry,
ManifestState,
Expand Down Expand Up @@ -174,6 +184,8 @@
"ExecutionRepository",
"ExecutionRepositoryError",
"ExecutionLifecycle",
"ExecutionProfile",
"CoordinatorFactory",
"CalculatorPlan",
"CheckPlan",
"FakeExecutionPlan",
Expand Down Expand Up @@ -212,6 +224,11 @@
"PublishedArtifact",
"RecipeValidationError",
"RuntimeHealth",
"ProviderHealthProbe",
"QualificationLifecycleConfig",
"QualificationProfileError",
"build_execution_lifecycle",
"parse_execution_profile",
"RollbackAuthorizer",
"SignedBundleInstaller",
"SignedRecipeManifest",
Expand Down
18 changes: 18 additions & 0 deletions backend/cortex_backend/execution/lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class LifecycleSnapshot:
state: LifecycleState
health: RuntimeHealth
recovered_job_ids: tuple[str, ...] = ()
profile: str = "disabled"

@property
def available(self) -> bool:
Expand All @@ -83,11 +84,21 @@ def __init__(
coordinator_factory: Callable[[ExecutionRepository], LifecycleCoordinator],
health_check: Callable[[], RuntimeHealth],
enabled: bool = False,
profile: str | None = None,
) -> None:
selected_profile = (
profile if profile is not None else ("disabled" if not enabled else "custom")
)
if (
not isinstance(selected_profile, str)
or _SAFE_HEALTH_CODE.fullmatch(selected_profile) is None
):
raise ValueError("execution lifecycle profile must be a bounded identifier")
self.repository = repository
self._coordinator_factory = coordinator_factory
self._health_check = health_check
self._enabled = enabled
self._profile = selected_profile
self._coordinator: LifecycleCoordinator | None = None
self._recovered_job_ids: tuple[str, ...] = ()
self._health = (
Expand All @@ -108,9 +119,16 @@ def snapshot(self) -> LifecycleSnapshot:
return LifecycleSnapshot(
state=self._state,
health=self._health,
profile=self._profile,
recovered_job_ids=self._recovered_job_ids,
)

@property
def profile(self) -> str:
"""Return the bounded profile label selected at construction time."""

return self._profile

@property
def coordinator(self) -> LifecycleCoordinator | None:
"""Expose a coordinator only while the lifecycle is fully ready."""
Expand Down
184 changes: 184 additions & 0 deletions backend/cortex_backend/execution/qualification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
"""Explicit local qualification-profile lifecycle composition.

The normal Cortex application does not construct a runnable recipe lifecycle.
This module provides the deliberate development/CI seam for callers that have
already built the fixed worker, installed it with disposable qualification
trust, and supplied a coordinator that owns the worker boundary.

The profile is intentionally narrower than an official release profile:

* ``disabled`` is the default and never calls a health probe or coordinator;
* ``qualification`` requires the qualification release gate, an injected
coordinator factory, and a provider-health probe; and
* missing or malformed qualification wiring becomes a blocked lifecycle, not a
host-process fallback or an implicitly enabled provider.

This module does not create a process, bind a broker, load a provider, or read a
worker path. Those actions remain responsibilities of the injected controls.
"""

from __future__ import annotations

from dataclasses import dataclass
import re
from collections.abc import Callable
from typing import Literal

from .lifecycle import ExecutionLifecycle, LifecycleCoordinator, RuntimeHealth
from .release_gate import RecipeRuntimeReleaseGate
from .repository import ExecutionRepository


ExecutionProfile = Literal["disabled", "qualification"]
_SAFE_CODE = re.compile(r"^[a-z][a-z0-9._-]{0,63}$")


class QualificationProfileError(ValueError):
"""Stable configuration error for an explicit local profile selection."""

def __init__(self, code: str) -> None:
if _SAFE_CODE.fullmatch(code) is None:
raise ValueError("invalid qualification profile code")
self.code = code
super().__init__("The local qualification profile configuration is invalid.")


ProviderHealthProbe = Callable[[RuntimeHealth], RuntimeHealth]
CoordinatorFactory = Callable[[ExecutionRepository], LifecycleCoordinator]


@dataclass(frozen=True, slots=True)
class QualificationLifecycleConfig:
"""Controls required before the local qualification lifecycle can start."""

release_gate: RecipeRuntimeReleaseGate
coordinator_factory: CoordinatorFactory
provider_health_check: ProviderHealthProbe

def __post_init__(self) -> None:
if not isinstance(self.release_gate, RecipeRuntimeReleaseGate):
raise TypeError("qualification release gate is required")
if self.release_gate.release_profile != "qualification":
raise QualificationProfileError("qualification_gate_required")
if not callable(self.coordinator_factory):
raise TypeError("qualification coordinator factory must be callable")
if not callable(self.provider_health_check):
raise TypeError("qualification provider health check must be callable")


def parse_execution_profile(value: str | None) -> ExecutionProfile:
"""Parse an explicit profile without accepting aliases or whitespace."""

if value is None:
return "disabled"
if value == "disabled":
return "disabled"
if value == "qualification":
return "qualification"
raise QualificationProfileError("execution_profile_invalid")


def _unconfigured_factory(_repository: ExecutionRepository) -> LifecycleCoordinator:
raise RuntimeError("qualification coordinator is not configured")


def _disabled_health() -> RuntimeHealth:
return RuntimeHealth.blocked(
code="runtime_disabled",
message="Execution runtime is disabled in this build.",
)


def _qualification_configuration_health() -> RuntimeHealth:
return RuntimeHealth.blocked(
code="qualification_configuration_missing",
message="The local qualification runtime is not configured.",
)


def _qualification_health(config: QualificationLifecycleConfig) -> RuntimeHealth:
"""Compose release and provider health without leaking internal details."""

try:
release_health = config.release_gate.health()
except Exception:
return RuntimeHealth.blocked(
code="qualification_gate_failed",
message="The local qualification controls could not be verified safely.",
)
if not isinstance(release_health, RuntimeHealth):
return RuntimeHealth.blocked(
code="qualification_gate_result_invalid",
message="The local qualification controls returned an invalid result.",
)
if not release_health.available:
return release_health
try:
provider_health = config.provider_health_check(release_health)
except Exception:
return RuntimeHealth.blocked(
code="qualification_provider_health_failed",
message="The fixed-function provider could not be verified safely.",
)
if not isinstance(provider_health, RuntimeHealth):
return RuntimeHealth.blocked(
code="qualification_provider_health_invalid",
message="The fixed-function provider returned an invalid health result.",
)
if not provider_health.available:
return provider_health
return RuntimeHealth.ready("Local recipe qualification controls are ready.")


def build_execution_lifecycle(
repository: ExecutionRepository,
*,
profile: str | None = None,
qualification: QualificationLifecycleConfig | None = None,
) -> ExecutionLifecycle:
"""Build the only supported local profile boundary.

``profile=None`` and ``profile="disabled"`` construct the same inert
lifecycle. Selecting ``qualification`` is explicit and still fails closed
when its controls are absent or unhealthy. The returned lifecycle carries a
safe profile label for diagnostics.
"""

selected = parse_execution_profile(profile)
if selected == "disabled":
if qualification is not None:
raise QualificationProfileError("qualification_config_with_disabled_profile")
return ExecutionLifecycle(
repository,
coordinator_factory=_unconfigured_factory,
health_check=_disabled_health,
enabled=False,
profile="disabled",
)

if qualification is None:
return ExecutionLifecycle(
repository,
coordinator_factory=_unconfigured_factory,
health_check=_qualification_configuration_health,
enabled=True,
profile="qualification",
)
return ExecutionLifecycle(
repository,
coordinator_factory=qualification.coordinator_factory,
health_check=lambda: _qualification_health(qualification),
enabled=True,
profile="qualification",
)


__all__ = [
"CoordinatorFactory",
"ExecutionProfile",
"ProviderHealthProbe",
"QualificationLifecycleConfig",
"QualificationProfileError",
"build_execution_lifecycle",
"parse_execution_profile",
]
6 changes: 6 additions & 0 deletions backend/cortex_backend/execution/release_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ def __init__(
self._platform_name = platform_name or os.name
self._release_profile = release_profile

@property
def release_profile(self) -> Literal["official", "qualification"]:
"""Return the immutable profile selected for this preflight."""

return self._release_profile

@staticmethod
def _blocked(
checks: list[ReleaseGateCheck],
Expand Down
13 changes: 8 additions & 5 deletions docs/adr/0001-capability-tiered-agentic-execution-harness.md
Original file line number Diff line number Diff line change
Expand Up @@ -997,11 +997,14 @@ presence, and (when maintainers choose to use it) explicit external-review evide
Quality CI now builds the fixed worker and runs the disposable
signed/AppContainer/broker, hostile-decoder, and cancellation corpus with bounded
timeouts; this is the open-source qualification evidence and does not need
production trust material. The next core slice is explicit lifecycle wiring for
`release_profile="qualification"`, with the application still default-off and the
profile documented as local/CI-only. The separate `recipe.release-review.v1`
verifier is available as optional release hardening; it remains observation-only
and no approval record or production trust material is committed.
production trust material. Explicit lifecycle composition for
`release_profile="qualification"` is now available through the documented
local/CI-only builder; the application remains default-off and no recipe request
route is enabled by this slice. The next core slice is the recipe-specific
coordinator/request and artifact-publication path behind that lifecycle. The
separate `recipe.release-review.v1` verifier is available as optional release
hardening; it remains observation-only and no approval record or production
trust material is committed.

### Phase 3 — `scratch.auto.v1` arbitrary WebAssembly code

Expand Down
Loading
Loading