"""Capability profile derived from active extensions (WikiEngineCoreArchitecture E-5). The engine's §A `CapabilityProfile` is **computed**, not hand-set: start from the kernel base profile, then fold each active extension's `on_profile` contribution (in the runtime's deterministic order), then `validate()`. This realizes the chain *configuration (which extensions) → capability (the profile) → conformance* — activating an extension raises the shard's advertised capabilities, and composition can never yield an impossible profile (validate rejects it, §6.5). """ from __future__ import annotations import dataclasses from dataclasses import dataclass from shard_wiki.engine.extension import ActiveExtensions, Hook from shard_wiki.model import ( AccessGrant, Addressing, AttachmentMode, CapabilityProfile, ContentOpacity, History, MergeModel, NativeQuery, OperationalEnvelope, Substrate, Translation, Verb, WriteGranularity, ) from shard_wiki.provenance import Liveness __all__ = ["engine_base_profile", "ProfileContribution", "derive_profile"] # Profile fields an extension may *raise* via on_profile (substrate/attachment are kernel-fixed). _OVERRIDABLE = ( "write_granularity", "content_opacity", "liveness", "history", "merge_model", "addressing", "native_query", "translation", "access_grant", ) def engine_base_profile() -> CapabilityProfile: """The kernel-only (no extensions) capability profile — the c2-minimum engine shard.""" return CapabilityProfile( substrate=Substrate.FILES, attachment_mode=AttachmentMode.IN_ENGINE_HOST, write_granularity=WriteGranularity.PER_PAGE, content_opacity=ContentOpacity.TRANSPARENT, operational_envelope=OperationalEnvelope.LOCAL_UNBOUNDED, access_grant=AccessGrant.OPEN, liveness=Liveness.STATIC, history=History.INTERNAL_ONLY, merge_model=MergeModel.NONE, addressing=Addressing.PATH, native_query=NativeQuery.NONE, translation=Translation.NATIVE, supported_verbs=frozenset({Verb.READ, Verb.WRITE}), ).validate() @dataclass(frozen=True, slots=True) class ProfileContribution: """An extension's contribution to the derived profile (returned from its ON_PROFILE hook). A non-``None`` axis overrides that axis; ``verbs_add`` are unioned in. Order = the runtime's deterministic dispatch order, so later extensions win on a contested axis.""" write_granularity: WriteGranularity | None = None content_opacity: ContentOpacity | None = None liveness: Liveness | None = None history: History | None = None merge_model: MergeModel | None = None addressing: Addressing | None = None native_query: NativeQuery | None = None translation: Translation | None = None access_grant: AccessGrant | None = None verbs_add: frozenset[Verb] = frozenset() def derive_profile( active: ActiveExtensions, base: CapabilityProfile | None = None ) -> CapabilityProfile: """Fold active extensions' ON_PROFILE contributions onto ``base`` and validate the result. Raises :class:`~shard_wiki.model.ProfileError` if the composed profile is impossible — so an activation set can never advertise an invalid capability profile. """ profile = base or engine_base_profile() contributions = active.dispatch_collect(Hook.ON_PROFILE) overrides: dict[str, object] = {} verbs: set[Verb] = set(profile.supported_verbs) for contrib in contributions: if not isinstance(contrib, ProfileContribution): continue for field_name in _OVERRIDABLE: value = getattr(contrib, field_name) if value is not None: overrides[field_name] = value verbs |= set(contrib.verbs_add) return dataclasses.replace( profile, supported_verbs=frozenset(verbs), **overrides ).validate()