diff --git a/ace/application/__init__.py b/ace/application/__init__.py index 21396e8..20b3f1e 100644 --- a/ace/application/__init__.py +++ b/ace/application/__init__.py @@ -330,6 +330,19 @@ IntelligenceBuilderSessionService, RegisteredSourceOptionProvider, ) +from ace.application.intelligence_builder_activation import ( + BuilderActivationBootstrapOutcome, + BuilderActivationPlanAdmission, + ExactCompiledPackResolver, + IntelligenceBuilderActivationError, + IntelligenceBuilderActivationService, +) +from ace.application.intelligence_builder_activation_contracts import ( + BUILDER_ACTIVATION_PLAN_ARTIFACT_VERSION, + BUILDER_ACTIVATION_RECEIPT_ARTIFACT_VERSION, + BuilderActivationPlanArtifactV1, + BuilderActivationReceiptArtifactV1, +) from ace.application.intelligence_builder_contracts import ( ConnectionEffect, IntelligenceBuilderSessionRevisionV1, @@ -785,6 +798,15 @@ "IMPACT_PROPOSAL_DISPOSITION_DECISION_TYPE", "IMPACT_PROPOSAL_DISPOSITION_RECORD_KIND", "IntelligenceBuilderArtifactAdmission", + "BuilderActivationBootstrapOutcome", + "BuilderActivationPlanAdmission", + "BuilderActivationPlanArtifactV1", + "BuilderActivationReceiptArtifactV1", + "BUILDER_ACTIVATION_PLAN_ARTIFACT_VERSION", + "BUILDER_ACTIVATION_RECEIPT_ARTIFACT_VERSION", + "ExactCompiledPackResolver", + "IntelligenceBuilderActivationError", + "IntelligenceBuilderActivationService", "IntelligenceBuilderSessionAdmission", "IntelligenceBuilderSessionError", "IntelligenceBuilderSessionReplayConflict", diff --git a/ace/application/brief_synthesis.py b/ace/application/brief_synthesis.py index 3e262dc..28de178 100644 --- a/ace/application/brief_synthesis.py +++ b/ace/application/brief_synthesis.py @@ -7,7 +7,6 @@ from typing import Callable, Literal from ace.application.domain_activation import ( - DOMAIN_ACTIVATION_STATE_KIND, CommittedActivationBinding, DomainActivationAdmissionError, DomainActivationAdmissionService, @@ -319,7 +318,7 @@ def _activation_precondition(binding: CommittedActivationBinding) -> GovernedSta revision = binding.prepared_binding.revision receipt = binding.commit_receipt return GovernedStateHeadPreconditionV1Alpha1( - state_kind=DOMAIN_ACTIVATION_STATE_KIND, + state_kind=receipt.state_kind, product_id=revision.spec.product_id, state_id=str(revision.activation_id), sequence=revision.revision, diff --git a/ace/application/domain_activation.py b/ace/application/domain_activation.py index d043da0..2001b2c 100644 --- a/ace/application/domain_activation.py +++ b/ace/application/domain_activation.py @@ -35,7 +35,8 @@ bind_prepared_activation, ) -DOMAIN_ACTIVATION_STATE_KIND = "domain_activation" +DOMAIN_ACTIVATION_STATE_KIND = "domain_activation_v1alpha1" +LEGACY_DOMAIN_ACTIVATION_STATE_KIND = "domain_activation" class DomainActivationAdmissionError(RuntimeError): @@ -104,7 +105,6 @@ def _validate_committed_pair( ) -> CommittedDomainActivation: envelope = _envelope(revision) expected = { - "state_kind": envelope.state_kind, "product_id": envelope.product_id, "state_id": envelope.state_id, "sequence": envelope.sequence, @@ -113,7 +113,10 @@ def _validate_committed_pair( "prior_revision_id": envelope.prior_revision_id, } actual = {name: getattr(receipt, name) for name in expected} - if actual != expected: + if ( + receipt.state_kind not in {DOMAIN_ACTIVATION_STATE_KIND, LEGACY_DOMAIN_ACTIVATION_STATE_KIND} + or actual != expected + ): raise DomainActivationAdmissionError("Core commit receipt does not bind the exact activation revision") return CommittedDomainActivation(revision=revision, commit_receipt=receipt) @@ -205,10 +208,16 @@ async def reload( product_id=product_id, state_id=activation_id, ) + if head is None: + head = await self.store.load_head( + state_kind=LEGACY_DOMAIN_ACTIVATION_STATE_KIND, + product_id=product_id, + state_id=activation_id, + ) if head is None: return None if ( - head.state_kind != DOMAIN_ACTIVATION_STATE_KIND + head.state_kind not in {DOMAIN_ACTIVATION_STATE_KIND, LEGACY_DOMAIN_ACTIVATION_STATE_KIND} or head.product_id != product_id or head.state_id != activation_id ): @@ -226,7 +235,6 @@ async def reload( expected_envelope = _envelope(revision) envelope_fields = ( "contract", - "state_kind", "product_id", "state_id", "sequence", @@ -236,7 +244,9 @@ async def reload( "approval_subject_ref", "payload_contract", ) - if any(getattr(expected_envelope, name) != getattr(envelope, name) for name in envelope_fields): + if envelope.state_kind not in {DOMAIN_ACTIVATION_STATE_KIND, LEGACY_DOMAIN_ACTIVATION_STATE_KIND} or any( + getattr(expected_envelope, name) != getattr(envelope, name) for name in envelope_fields + ): raise DomainActivationAdmissionError("persisted envelope does not match exact activation material") if ( head.sequence != revision.revision @@ -283,7 +293,6 @@ async def load_exact( raise DomainActivationAdmissionError("historical activation revision failed exact revalidation") from None envelope_fields = ( "contract", - "state_kind", "product_id", "state_id", "sequence", @@ -293,7 +302,9 @@ async def load_exact( "approval_subject_ref", "payload_contract", ) - if any(getattr(expected_envelope, name) != getattr(envelope, name) for name in envelope_fields): + if envelope.state_kind not in {DOMAIN_ACTIVATION_STATE_KIND, LEGACY_DOMAIN_ACTIVATION_STATE_KIND} or any( + getattr(expected_envelope, name) != getattr(envelope, name) for name in envelope_fields + ): raise DomainActivationAdmissionError("historical activation envelope changed from exact revision material") return _validate_committed_pair(revision, receipt) @@ -317,6 +328,7 @@ def bind_committed_activation( "CommittedActivationBinding", "CommittedDomainActivation", "DOMAIN_ACTIVATION_STATE_KIND", + "LEGACY_DOMAIN_ACTIVATION_STATE_KIND", "DomainActivationAdmissionError", "DomainActivationAdmissionService", "bind_committed_activation", diff --git a/ace/application/domain_activation_plan.py b/ace/application/domain_activation_plan.py index b79d758..5fe0bdc 100644 --- a/ace/application/domain_activation_plan.py +++ b/ace/application/domain_activation_plan.py @@ -11,7 +11,7 @@ from typing import Literal from ace.application.briefing_agent_contracts import FirstBriefingPreviewV1 -from ace.application.domain_activation import DOMAIN_ACTIVATION_STATE_KIND +from ace.application.domain_activation import LEGACY_DOMAIN_ACTIVATION_STATE_KIND from ace.application.domain_activation_plan_contracts import ( DOMAIN_ACTIVATION_REVISION_V1ALPHA2_VERSION, ActivationOnboardingHandoffV1Alpha2, @@ -48,6 +48,9 @@ class DomainActivationPlanAdmissionError(RuntimeError): """Exact-plan activation material failed closed before durable effect.""" +DOMAIN_ACTIVATION_PLAN_STATE_KIND = "domain_activation_plan_v1alpha2" + + @dataclass(frozen=True, slots=True) class CommittedDomainActivationPlan: revision: DomainActivationRevisionV1Alpha2 @@ -300,7 +303,7 @@ def _envelope(revision: DomainActivationRevisionV1Alpha2) -> GovernedStateRevisi ): raise DomainActivationPlanAdmissionError("v1alpha2 activation material is missing a derived identity") return GovernedStateRevisionV1( - state_kind=DOMAIN_ACTIVATION_STATE_KIND, + state_kind=DOMAIN_ACTIVATION_PLAN_STATE_KIND, product_id=revision.plan.spec.product_id, state_id=revision.activation_id, sequence=revision.revision, @@ -320,7 +323,6 @@ def _validate_committed_pair( receipt = _revalidate_commit_receipt(receipt) envelope = _envelope(revision) expected = { - "state_kind": envelope.state_kind, "product_id": envelope.product_id, "state_id": envelope.state_id, "sequence": envelope.sequence, @@ -328,7 +330,9 @@ def _validate_committed_pair( "material_hash": envelope.material_hash, "prior_revision_id": envelope.prior_revision_id, } - if any(getattr(receipt, name) != value for name, value in expected.items()): + if receipt.state_kind not in {DOMAIN_ACTIVATION_PLAN_STATE_KIND, LEGACY_DOMAIN_ACTIVATION_STATE_KIND} or any( + getattr(receipt, name) != value for name, value in expected.items() + ): raise DomainActivationPlanAdmissionError( "Core commit receipt does not bind the exact v1alpha2 activation revision" ) @@ -444,7 +448,6 @@ def _parse_persisted_revision( expected = _envelope(revision) fields = ( "contract", - "state_kind", "product_id", "state_id", "sequence", @@ -454,7 +457,9 @@ def _parse_persisted_revision( "approval_subject_ref", "payload_contract", ) - if any(getattr(expected, name) != getattr(envelope, name) for name in fields): + if envelope.state_kind not in {DOMAIN_ACTIVATION_PLAN_STATE_KIND, LEGACY_DOMAIN_ACTIVATION_STATE_KIND} or any( + getattr(expected, name) != getattr(envelope, name) for name in fields + ): raise DomainActivationPlanAdmissionError("persisted envelope does not match exact v1alpha2 activation material") return revision @@ -471,7 +476,7 @@ async def _current( revision: DomainActivationRevisionV1Alpha2, ) -> DomainActivationRevisionV1Alpha2 | None: head = await self.store.load_head( - state_kind=DOMAIN_ACTIVATION_STATE_KIND, + state_kind=DOMAIN_ACTIVATION_PLAN_STATE_KIND, product_id=revision.plan.spec.product_id, state_id=str(revision.activation_id), ) @@ -669,10 +674,16 @@ async def reload( activation_id = f"domain_activation:{canonical_hash([product_id, activation_key])[:32]}" head = await self.store.load_head( - state_kind=DOMAIN_ACTIVATION_STATE_KIND, + state_kind=DOMAIN_ACTIVATION_PLAN_STATE_KIND, product_id=product_id, state_id=activation_id, ) + if head is None: + head = await self.store.load_head( + state_kind=LEGACY_DOMAIN_ACTIVATION_STATE_KIND, + product_id=product_id, + state_id=activation_id, + ) if head is None: return None envelope = await self.store.load_revision(head.revision_id, product_id=product_id) @@ -699,6 +710,7 @@ async def reload( "CommittedDomainActivationPlan", "DomainActivationPlanAdmissionError", "DomainActivationPlanAdmissionService", + "DOMAIN_ACTIVATION_PLAN_STATE_KIND", "activation_commit_reference", "prepare_activation_onboarding_handoff", "validate_activation_commit_reference", diff --git a/ace/application/intelligence_builder.py b/ace/application/intelligence_builder.py index 92cd53f..d529588 100644 --- a/ace/application/intelligence_builder.py +++ b/ace/application/intelligence_builder.py @@ -17,6 +17,10 @@ IntelligenceModelDispositionV1, IntelligenceModelProposalV1, ) +from ace.application.intelligence_builder_activation_contracts import ( + BuilderActivationPlanArtifactV1, + BuilderActivationReceiptArtifactV1, +) from ace.application.intelligence_builder_contracts import ( ONBOARDING_SESSION_REVISION_VERSION, ConnectionEffect, @@ -57,6 +61,8 @@ | IntelligenceModelProposalV1 | IntelligenceModelDispositionV1 | FirstBriefingPreviewV1 + | BuilderActivationPlanArtifactV1 + | BuilderActivationReceiptArtifactV1 ) OnboardingArtifactT = TypeVar( "OnboardingArtifactT", @@ -68,6 +74,8 @@ IntelligenceModelProposalV1, IntelligenceModelDispositionV1, FirstBriefingPreviewV1, + BuilderActivationPlanArtifactV1, + BuilderActivationReceiptArtifactV1, ) @@ -216,6 +224,10 @@ def _artifact_material(artifact: OnboardingArtifact) -> tuple[str, str, datetime return str(artifact.disposition_id), str(artifact.disposition_digest), artifact.approved_at if isinstance(artifact, FirstBriefingPreviewV1): return str(artifact.brief_id), str(artifact.brief_digest), artifact.generated_at + if isinstance(artifact, BuilderActivationPlanArtifactV1): + return str(artifact.artifact_id), str(artifact.artifact_digest), artifact.created_at + if isinstance(artifact, BuilderActivationReceiptArtifactV1): + return str(artifact.artifact_id), str(artifact.artifact_digest), artifact.activated_at raise TypeError("unsupported onboarding artifact contract") @@ -236,6 +248,10 @@ def _artifact_kind(artifact: OnboardingArtifact) -> OnboardingArtifactKind: return OnboardingArtifactKind.INTELLIGENCE_MODEL_DISPOSITION if isinstance(artifact, FirstBriefingPreviewV1): return OnboardingArtifactKind.FIRST_BRIEFING_PREVIEW + if isinstance(artifact, BuilderActivationPlanArtifactV1): + return OnboardingArtifactKind.ACTIVATION_PLAN + if isinstance(artifact, BuilderActivationReceiptArtifactV1): + return OnboardingArtifactKind.ACTIVATION_RECEIPT raise TypeError("unsupported onboarding artifact contract") @@ -305,6 +321,10 @@ async def persist_artifact( exact = IntelligenceModelDispositionV1.model_validate(artifact.model_dump(mode="python")) elif isinstance(artifact, FirstBriefingPreviewV1): exact = FirstBriefingPreviewV1.model_validate(artifact.model_dump(mode="python")) + elif isinstance(artifact, BuilderActivationPlanArtifactV1): + exact = BuilderActivationPlanArtifactV1.model_validate(artifact.model_dump(mode="python")) + elif isinstance(artifact, BuilderActivationReceiptArtifactV1): + exact = BuilderActivationReceiptArtifactV1.model_validate(artifact.model_dump(mode="python")) else: raise IntelligenceBuilderSessionError("unsupported onboarding artifact failed closed") artifact_id, artifact_digest, occurred_at = _artifact_material(exact) @@ -540,6 +560,18 @@ async def load_latest( raise IntelligenceBuilderSessionError("onboarding session history lost exact chain continuity") return revisions[-1] + async def reload_admission( + self, + revision: IntelligenceBuilderSessionRevisionV1, + ) -> IntelligenceBuilderSessionAdmission: + """Reopen one exact durable session revision and its append receipt.""" + + validated = IntelligenceBuilderSessionRevisionV1.model_validate(revision.model_dump(mode="python")) + admission = await self._replay(validated) + if admission is None: + raise IntelligenceBuilderSessionError("onboarding revision is not durably admitted") + return admission + async def start( self, *, diff --git a/ace/application/intelligence_builder_activation.py b/ace/application/intelligence_builder_activation.py new file mode 100644 index 0000000..23e2b28 --- /dev/null +++ b/ace/application/intelligence_builder_activation.py @@ -0,0 +1,284 @@ +"""Core-owned bootstrap from a reviewed Builder plan to canonical activation.""" + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Protocol + +from ace.application.domain_activation import ( + CommittedActivationBinding, + DomainActivationAdmissionService, + bind_committed_activation, +) +from ace.application.domain_activation_compatibility import DomainActivationCompatibilityService +from ace.application.domain_activation_plan import ( + CommittedDomainActivationPlan, + DomainActivationPlanAdmissionService, + activation_commit_reference, +) +from ace.application.intelligence_builder import ( + IntelligenceBuilderArtifactAdmission, + IntelligenceBuilderSessionAdmission, + IntelligenceBuilderSessionService, +) +from ace.application.intelligence_builder_activation_contracts import ( + BuilderActivationPlanArtifactV1, + BuilderActivationReceiptArtifactV1, +) +from ace.application.intelligence_builder_contracts import ( + OnboardingArtifactKind, + OnboardingArtifactReferenceV1, + OnboardingStage, + OnboardingTransitionAuthority, +) +from ace.intelligence.contracts.activation import CompiledPackRefV1 +from ace.intelligence.contracts.pack import CompiledDomainPackV1 +from ace.intelligence.contracts.resources import ActivationRevisionReferenceV1Alpha1 + + +class IntelligenceBuilderActivationError(RuntimeError): + """The durable Builder activation boundary failed closed.""" + + +class ExactCompiledPackResolver(Protocol): + async def load_exact(self, *, reference: CompiledPackRefV1) -> CompiledDomainPackV1 | None: ... + + +@dataclass(frozen=True, slots=True) +class BuilderActivationPlanAdmission: + artifact: BuilderActivationPlanArtifactV1 + artifact_admission: IntelligenceBuilderArtifactAdmission + session: IntelligenceBuilderSessionAdmission + + +@dataclass(frozen=True, slots=True) +class BuilderActivationBootstrapOutcome: + binding: CommittedActivationBinding + receipt_artifact: BuilderActivationReceiptArtifactV1 + artifact_admission: IntelligenceBuilderArtifactAdmission + session: IntelligenceBuilderSessionAdmission + replayed: bool + + +class IntelligenceBuilderActivationService: + """Reload exact durable Builder material, then commit canonical activation.""" + + def __init__( + self, + *, + sessions: IntelligenceBuilderSessionService, + plans: DomainActivationPlanAdmissionService, + compatibility: DomainActivationCompatibilityService, + canonical: DomainActivationAdmissionService, + packs: ExactCompiledPackResolver, + ) -> None: + self.sessions = sessions + self.plans = plans + self.compatibility = compatibility + self.canonical = canonical + self.packs = packs + + async def record_current_plan( + self, + *, + product_id: str, + session_id: str, + committed: CommittedDomainActivationPlan, + pack: CompiledPackRefV1, + recorded_at: datetime, + ) -> BuilderActivationPlanAdmission: + current = await self.sessions.load_latest( + product_id=product_id, + session_id=session_id, + available_at=recorded_at, + ) + if current is None or current.stage is not OnboardingStage.FIRST_BRIEFING_READY: + raise IntelligenceBuilderActivationError( + "activation plan requires the exact current briefing-ready session" + ) + source = committed.revision + admitted = await self.plans.reload( + product_id=product_id, + activation_key=source.plan.spec.activation_key, + ) + if admitted is None or admitted != committed: + raise IntelligenceBuilderActivationError("activation plan is not the exact current Core-admitted revision") + if ( + source.plan.spec.product_id != product_id + or source.plan.onboarding_handoff.session_id != session_id + or source.plan.onboarding_handoff.session_revision_id != current.revision_id + or source.plan.spec.pack != pack + ): + raise IntelligenceBuilderActivationError("activation plan crossed its exact session, product, or Pack") + reference = activation_commit_reference(committed) + artifact = BuilderActivationPlanArtifactV1( + session_id=session_id, + session_revision_id=str(current.revision_id), + session_revision_digest=str(current.revision_digest), + source_commit=reference, + spec_id=str(source.plan.spec.spec_id), + spec_digest=f"sha256:{source.plan.spec.spec_hash}", + pack=pack, + created_at=recorded_at, + ) + artifact_admission = await self.sessions.persist_artifact(product_id=product_id, artifact=artifact) + artifacts = tuple( + item for item in current.artifacts if item.artifact_kind is not OnboardingArtifactKind.ACTIVATION_PLAN + ) + ( + OnboardingArtifactReferenceV1( + artifact_kind=OnboardingArtifactKind.ACTIVATION_PLAN, + artifact_id=str(artifact.artifact_id), + artifact_digest=str(artifact.artifact_digest), + ), + ) + session = await self.sessions.advance( + current, + stage=OnboardingStage.ACTIVATION_PENDING, + authority=OnboardingTransitionAuthority.AGENT_PROPOSAL, + actor_ref=source.actor_ref, + occurred_at=recorded_at, + artifacts=artifacts, + ) + return BuilderActivationPlanAdmission( + artifact=artifact, + artifact_admission=artifact_admission, + session=session, + ) + + async def activate( + self, + *, + product_id: str, + session_id: str, + activation_approval_receipt_ref: str, + evaluated_at: datetime, + ) -> BuilderActivationBootstrapOutcome: + try: + current = await self.sessions.load_latest( + product_id=product_id, + session_id=session_id, + available_at=evaluated_at, + ) + if current is None or current.stage not in {OnboardingStage.ACTIVATION_PENDING, OnboardingStage.ACTIVE}: + raise IntelligenceBuilderActivationError("activation requires the exact current pending session") + plan_refs = [ + item for item in current.artifacts if item.artifact_kind is OnboardingArtifactKind.ACTIVATION_PLAN + ] + if len(plan_refs) != 1: + raise IntelligenceBuilderActivationError("current session must bind one exact durable activation plan") + plan_artifact = await self.sessions.load_artifact( + product_id=product_id, + reference=plan_refs[0], + artifact_type=BuilderActivationPlanArtifactV1, + available_at=evaluated_at, + ) + source = await self.plans.reload( + product_id=product_id, + activation_key=plan_artifact.source_commit.activation_key, + ) + if source is None or activation_commit_reference(source) != plan_artifact.source_commit: + raise IntelligenceBuilderActivationError("durable activation plan is stale or no longer current") + revision = source.revision + if ( + plan_artifact.session_id != session_id + or plan_artifact.session_revision_id != revision.plan.onboarding_handoff.session_revision_id + or plan_artifact.spec_id != revision.plan.spec.spec_id + or plan_artifact.spec_digest != f"sha256:{revision.plan.spec.spec_hash}" + or plan_artifact.pack != revision.plan.spec.pack + ): + raise IntelligenceBuilderActivationError("durable activation plan linkage changed") + prepared = await self.compatibility.prepare_initial_canonical_activation( + revision=revision, + activation_approval_receipt_ref=activation_approval_receipt_ref, + evaluated_at=evaluated_at, + ) + pack = await self.packs.load_exact(reference=plan_artifact.pack) + if pack is None: + raise IntelligenceBuilderActivationError("exact compiled Pack is unavailable") + canonical = await self.canonical.reload( + product_id=product_id, + activation_key=revision.plan.spec.activation_key, + ) + replayed = canonical is not None + if canonical is None: + canonical = await self.canonical.admit( + prepared.canonical_revision, + expected_head_revision_id=None, + committed_at=evaluated_at, + ) + elif canonical.revision != prepared.canonical_revision: + raise IntelligenceBuilderActivationError("canonical activation head already binds different material") + binding = bind_committed_activation(pack=pack, committed=canonical) + canonical_reference = ActivationRevisionReferenceV1Alpha1( + product_id=product_id, + activation_key=canonical.revision.spec.activation_key, + activation_id=str(canonical.revision.activation_id), + revision=canonical.revision.revision, + revision_id=str(canonical.revision.revision_id), + revision_digest=f"sha256:{canonical.revision.revision_hash}", + ) + receipt_artifact = BuilderActivationReceiptArtifactV1( + session_id=session_id, + activation_plan_artifact_id=str(plan_artifact.artifact_id), + activation_plan_artifact_digest=str(plan_artifact.artifact_digest), + source_commit=plan_artifact.source_commit, + canonical_revision=canonical_reference, + canonical_state_kind=canonical.commit_receipt.state_kind, + canonical_commit_receipt_id=str(canonical.commit_receipt.receipt_id), + canonical_commit_receipt_digest=f"sha256:{canonical.commit_receipt.receipt_hash}", + activated_at=canonical.commit_receipt.committed_at, + ) + artifact_admission = await self.sessions.persist_artifact( + product_id=product_id, + artifact=receipt_artifact, + ) + receipt_ref = OnboardingArtifactReferenceV1( + artifact_kind=OnboardingArtifactKind.ACTIVATION_RECEIPT, + artifact_id=str(receipt_artifact.artifact_id), + artifact_digest=str(receipt_artifact.artifact_digest), + ) + artifacts = tuple( + item + for item in current.artifacts + if item.artifact_kind is not OnboardingArtifactKind.ACTIVATION_RECEIPT + ) + (receipt_ref,) + if current.stage is OnboardingStage.ACTIVE: + if ( + receipt_ref not in current.artifacts + or current.approval_receipt_ref != activation_approval_receipt_ref + ): + raise IntelligenceBuilderActivationError( + "active Builder session does not bind the exact activation receipt" + ) + session = await self.sessions.reload_admission(current) + else: + session = await self.sessions.advance( + current, + stage=OnboardingStage.ACTIVE, + authority=OnboardingTransitionAuthority.CORE_ACTIVATION, + actor_ref=revision.actor_ref, + occurred_at=evaluated_at, + artifacts=artifacts, + approval_receipt_ref=activation_approval_receipt_ref, + ) + return BuilderActivationBootstrapOutcome( + binding=binding, + receipt_artifact=receipt_artifact, + artifact_admission=artifact_admission, + session=session, + replayed=replayed, + ) + except IntelligenceBuilderActivationError: + raise + except Exception as exc: + raise IntelligenceBuilderActivationError("Builder activation bootstrap failed closed") from exc + + +__all__ = [ + "BuilderActivationBootstrapOutcome", + "BuilderActivationPlanAdmission", + "ExactCompiledPackResolver", + "IntelligenceBuilderActivationError", + "IntelligenceBuilderActivationService", +] diff --git a/ace/application/intelligence_builder_activation_contracts.py b/ace/application/intelligence_builder_activation_contracts.py new file mode 100644 index 0000000..29aabc1 --- /dev/null +++ b/ace/application/intelligence_builder_activation_contracts.py @@ -0,0 +1,134 @@ +"""Exact durable Builder artifacts for the activation boundary.""" + +from __future__ import annotations + +from datetime import datetime +from typing import Literal + +from pydantic import field_validator, model_validator + +from ace.application.domain_activation_plan_contracts import DomainActivationCommitReferenceV1Alpha2 +from ace.application.intelligence_builder_contracts import ( + IntelligenceBuilderContract, + aware_datetime, + derive_builder_identity, +) +from ace.intelligence.contracts.activation import CompiledPackRefV1 +from ace.intelligence.contracts.common import validate_digest, validate_reference +from ace.intelligence.contracts.resources import ActivationRevisionReferenceV1Alpha1 + +BUILDER_ACTIVATION_PLAN_ARTIFACT_VERSION = "ace.application.builder-activation-plan-artifact/v1alpha1" +BUILDER_ACTIVATION_RECEIPT_ARTIFACT_VERSION = "ace.application.builder-activation-receipt-artifact/v1alpha1" + + +class BuilderActivationPlanArtifactV1(IntelligenceBuilderContract): + """Exact committed v1alpha2 plan linked to its Builder session and Pack ref.""" + + contract: Literal["ace.application.builder-activation-plan-artifact/v1alpha1"] = ( + BUILDER_ACTIVATION_PLAN_ARTIFACT_VERSION + ) + session_id: str + session_revision_id: str + session_revision_digest: str + source_commit: DomainActivationCommitReferenceV1Alpha2 + spec_id: str + spec_digest: str + pack: CompiledPackRefV1 + created_at: datetime + artifact_id: str | None = None + artifact_digest: str | None = None + + @field_validator("session_id", "session_revision_id", "spec_id", "artifact_id") + @classmethod + def validate_refs(cls, value: str | None, info) -> str | None: + return validate_reference(value, name=info.field_name) if value is not None else None + + @field_validator("session_revision_digest", "spec_digest", "artifact_digest") + @classmethod + def validate_digests(cls, value: str | None, info) -> str | None: + return validate_digest(value) if value is not None else None + + @field_validator("created_at") + @classmethod + def validate_time(cls, value: datetime) -> datetime: + return aware_datetime(value, name="created_at") + + @model_validator(mode="after") + def derive_identity(self): + if self.created_at < self.source_commit.committed_at: + raise ValueError("activation-plan artifact cannot predate its governed commit") + derive_builder_identity( + self, + prefix="builder_activation_plan_artifact", + id_field="artifact_id", + digest_field="artifact_digest", + ) + return self + + +class BuilderActivationReceiptArtifactV1(IntelligenceBuilderContract): + """Non-authorizing linkage from a Builder plan to canonical activation.""" + + contract: Literal["ace.application.builder-activation-receipt-artifact/v1alpha1"] = ( + BUILDER_ACTIVATION_RECEIPT_ARTIFACT_VERSION + ) + session_id: str + activation_plan_artifact_id: str + activation_plan_artifact_digest: str + source_commit: DomainActivationCommitReferenceV1Alpha2 + canonical_revision: ActivationRevisionReferenceV1Alpha1 + canonical_state_kind: Literal["domain_activation_v1alpha1", "domain_activation"] + canonical_commit_receipt_id: str + canonical_commit_receipt_digest: str + activated_at: datetime + artifact_id: str | None = None + artifact_digest: str | None = None + + @field_validator( + "session_id", + "activation_plan_artifact_id", + "canonical_commit_receipt_id", + "artifact_id", + ) + @classmethod + def validate_refs(cls, value: str | None, info) -> str | None: + return validate_reference(value, name=info.field_name) if value is not None else None + + @field_validator( + "activation_plan_artifact_digest", + "canonical_commit_receipt_digest", + "artifact_digest", + ) + @classmethod + def validate_digests(cls, value: str | None, info) -> str | None: + return validate_digest(value) if value is not None else None + + @field_validator("activated_at") + @classmethod + def validate_time(cls, value: datetime) -> datetime: + return aware_datetime(value, name="activated_at") + + @model_validator(mode="after") + def validate_linkage_and_derive(self): + if ( + self.source_commit.product_id != self.canonical_revision.product_id + or self.source_commit.activation_key != self.canonical_revision.activation_key + or self.source_commit.activation_id != self.canonical_revision.activation_id + or self.activated_at < self.source_commit.committed_at + ): + raise ValueError("activation receipt crossed its exact source/canonical scope") + derive_builder_identity( + self, + prefix="builder_activation_receipt_artifact", + id_field="artifact_id", + digest_field="artifact_digest", + ) + return self + + +__all__ = [ + "BUILDER_ACTIVATION_PLAN_ARTIFACT_VERSION", + "BUILDER_ACTIVATION_RECEIPT_ARTIFACT_VERSION", + "BuilderActivationPlanArtifactV1", + "BuilderActivationReceiptArtifactV1", +] diff --git a/ace/application/live_intelligence_bridge.py b/ace/application/live_intelligence_bridge.py index 4cf081b..cdb88ab 100644 --- a/ace/application/live_intelligence_bridge.py +++ b/ace/application/live_intelligence_bridge.py @@ -8,7 +8,6 @@ from pydantic import BaseModel, TypeAdapter from ace.application.domain_activation import ( - DOMAIN_ACTIVATION_STATE_KIND, CommittedActivationBinding, DomainActivationAdmissionService, bind_committed_activation, @@ -160,7 +159,7 @@ def _activation_precondition( revision = binding.prepared_binding.revision receipt = binding.commit_receipt return GovernedStateHeadPreconditionV1Alpha1( - state_kind=DOMAIN_ACTIVATION_STATE_KIND, + state_kind=receipt.state_kind, product_id=revision.spec.product_id, state_id=str(revision.activation_id), sequence=revision.revision, diff --git a/ace/application/live_source_ingress.py b/ace/application/live_source_ingress.py index c1ec698..266f460 100644 --- a/ace/application/live_source_ingress.py +++ b/ace/application/live_source_ingress.py @@ -11,7 +11,6 @@ from pydantic import TypeAdapter from ace.application.domain_activation import ( - DOMAIN_ACTIVATION_STATE_KIND, DomainActivationAdmissionError, DomainActivationAdmissionService, bind_committed_activation, @@ -131,7 +130,7 @@ def _activation_head(committed) -> GovernedStateHeadPreconditionV1Alpha1: if revision.activation_id is None or revision.revision_id is None or receipt.receipt_id is None: raise LiveSourceIngressError("committed activation is missing exact head coordinates") return GovernedStateHeadPreconditionV1Alpha1( - state_kind=DOMAIN_ACTIVATION_STATE_KIND, + state_kind=receipt.state_kind, product_id=revision.spec.product_id, state_id=revision.activation_id, sequence=revision.revision, diff --git a/ace/application/recorded_source_admission.py b/ace/application/recorded_source_admission.py index 0269064..749bb21 100644 --- a/ace/application/recorded_source_admission.py +++ b/ace/application/recorded_source_admission.py @@ -244,7 +244,7 @@ def validate_governed_recorded_admission(self) -> Self: ): raise ValueError("build authority use does not bind the exact recorded admission") if ( - self.activation_head_precondition.state_kind != DOMAIN_ACTIVATION_STATE_KIND + self.activation_head_precondition.state_kind not in {DOMAIN_ACTIVATION_STATE_KIND, "domain_activation"} or self.activation_head_precondition.product_id != self.product_id or self.activation_head_precondition.state_id != self.activation_revision.activation_id or self.activation_head_precondition.sequence != self.activation_revision.revision @@ -288,7 +288,7 @@ def _activation_head(binding: CommittedActivationBinding) -> GovernedStateHeadPr if revision.activation_id is None or revision.revision_id is None or receipt.receipt_id is None: raise RecordedSourceAdmissionError("committed activation is missing exact head coordinates") return GovernedStateHeadPreconditionV1Alpha1( - state_kind=DOMAIN_ACTIVATION_STATE_KIND, + state_kind=receipt.state_kind, product_id=revision.spec.product_id, state_id=revision.activation_id, sequence=revision.revision, diff --git a/ace/intelligence/contracts/source_acquisition.py b/ace/intelligence/contracts/source_acquisition.py index 707ae05..7f489e1 100644 --- a/ace/intelligence/contracts/source_acquisition.py +++ b/ace/intelligence/contracts/source_acquisition.py @@ -529,7 +529,7 @@ def validate_scope_and_identity(self) -> Self: raise ValueError("source mapping does not bind the exact admitted activation") if ( self.activation_head_precondition.product_id != self.product_id - or self.activation_head_precondition.state_kind != "domain_activation" + or self.activation_head_precondition.state_kind not in {"domain_activation", "domain_activation_v1alpha1"} or self.activation_head_precondition.state_id != self.activation_revision.activation_id or self.source_definition_head_precondition.product_id != self.product_id or self.source_definition_head_precondition.state_kind != "source_definition" diff --git a/tests/intelligence/test_brief_synthesis.py b/tests/intelligence/test_brief_synthesis.py index ced5e74..4b8444a 100644 --- a/tests/intelligence/test_brief_synthesis.py +++ b/tests/intelligence/test_brief_synthesis.py @@ -868,13 +868,13 @@ async def test_live_then_fresh_service_replay_is_exact_authorized_and_provider_o ) assert first.brief.resource_id == "brief:52d3d753b9b2ee30d1a8faaa316e1652" assert first.brief.resource_digest == ("sha256:52d3d753b9b2ee30d1a8faaa316e16526b3a6e5e4cf793d8417df8b91fe6a206") - assert first.synthesis_receipt.receipt_id == ("brief_synthesis_receipt:64f37fcb53080876222caf5f2d54eeea") + assert first.synthesis_receipt.receipt_id == ("brief_synthesis_receipt:13083446bbc36acc4ae97e2a96a22700") assert first.synthesis_receipt.receipt_digest == ( - "sha256:64f37fcb53080876222caf5f2d54eeea7a521339963a421a08df47f06c409c16" + "sha256:13083446bbc36acc4ae97e2a96a22700087d98c9b4e5b3145a68c8b94439dfc1" ) assert first.transaction_receipt.receipt_id == ("append_only_receipt:a0f17f23345df62697e317b927484ef1") assert first.transaction_receipt.request_hash == ( - "sha256:99e04efd57a75d092329c27e222e3c31fa0656c65bc0fb4a9ded1ca99891cd6b" + "sha256:ce69f1018e1ea95d8b262626bbfe15580563cb999cb0b0b51b05f6960aa0d35b" ) assert env.provider.calls == 1 assert env.runtime.capability_calls == env.runtime.authority_calls == 4 diff --git a/tests/intelligence/test_domain_activation_admission.py b/tests/intelligence/test_domain_activation_admission.py index 35e60f0..a5183b0 100644 --- a/tests/intelligence/test_domain_activation_admission.py +++ b/tests/intelligence/test_domain_activation_admission.py @@ -7,6 +7,7 @@ from ace.application.domain_activation import ( DOMAIN_ACTIVATION_STATE_KIND, + LEGACY_DOMAIN_ACTIVATION_STATE_KIND, DomainActivationAdmissionError, DomainActivationAdmissionService, ) @@ -245,6 +246,56 @@ async def test_reload_reconstructs_exact_committed_head_but_not_live_authority() ) +@pytest.mark.asyncio +async def test_reload_accepts_only_matching_legacy_v1alpha1_material(): + product_id = "product:legacy-reload" + spec = _spec(product_id=product_id) + occurred_at = datetime(2026, 8, 6, 12, tzinfo=UTC) + revision = _revision(spec=spec, receipt_ref="approval:legacy", occurred_at=occurred_at) + store = _MemoryStore() + committed = await DomainActivationAdmissionService(store=store, authority=_Authority()).admit( + revision, + expected_head_revision_id=None, + committed_at=occurred_at + timedelta(seconds=1), + ) + key = (DOMAIN_ACTIVATION_STATE_KIND, product_id, str(revision.activation_id)) + head = store.heads.pop(key) + envelope = store.revisions[(product_id, str(revision.revision_id))] + store.revisions[(product_id, str(revision.revision_id))] = envelope.model_copy( + update={"state_kind": LEGACY_DOMAIN_ACTIVATION_STATE_KIND} + ) + receipt = store.receipts[(product_id, committed.commit_receipt.receipt_id)] + receipt_material = receipt.model_dump(mode="python", exclude={"audit_id", "receipt_id", "receipt_hash"}) + receipt_material["state_kind"] = LEGACY_DOMAIN_ACTIVATION_STATE_KIND + legacy_receipt = type(receipt).model_validate(receipt_material) + del store.receipts[(product_id, committed.commit_receipt.receipt_id)] + store.receipts[(product_id, legacy_receipt.receipt_id)] = legacy_receipt + legacy_head = type(head).model_validate( + { + **head.model_dump(mode="python", exclude={"head_id", "commit_receipt_id", "state_kind"}), + "state_kind": LEGACY_DOMAIN_ACTIVATION_STATE_KIND, + "commit_receipt_id": legacy_receipt.receipt_id, + } + ) + store.heads[(LEGACY_DOMAIN_ACTIVATION_STATE_KIND, product_id, str(revision.activation_id))] = legacy_head + + reloaded = await DomainActivationAdmissionService(store=store, authority=_Authority()).reload( + product_id=product_id, + activation_key=spec.activation_key, + ) + assert reloaded is not None + assert reloaded.commit_receipt.state_kind == LEGACY_DOMAIN_ACTIVATION_STATE_KIND + + store.revisions[(product_id, str(revision.revision_id))] = envelope.model_copy( + update={"state_kind": LEGACY_DOMAIN_ACTIVATION_STATE_KIND, "payload_contract": "wrong.contract/v1"} + ) + with pytest.raises(DomainActivationAdmissionError, match="unsupported contract"): + await DomainActivationAdmissionService(store=store, authority=_Authority()).reload( + product_id=product_id, + activation_key=spec.activation_key, + ) + + @pytest.mark.integration @pytest.mark.asyncio async def test_surreal_commit_is_atomic_current_head_checked_and_restart_reloadable(db_pool): diff --git a/tests/intelligence/test_domain_activation_plan_admission.py b/tests/intelligence/test_domain_activation_plan_admission.py index 8dc3201..72ee363 100644 --- a/tests/intelligence/test_domain_activation_plan_admission.py +++ b/tests/intelligence/test_domain_activation_plan_admission.py @@ -8,8 +8,13 @@ import pytest from pydantic import ValidationError -from ace.application.domain_activation import DomainActivationAdmissionService +from ace.application.domain_activation import ( + LEGACY_DOMAIN_ACTIVATION_STATE_KIND, + DomainActivationAdmissionService, +) +from ace.application.domain_activation_compatibility import DomainActivationCompatibilityService from ace.application.domain_activation_plan import ( + DOMAIN_ACTIVATION_PLAN_STATE_KIND, DomainActivationPlanAdmissionError, DomainActivationPlanAdmissionService, activation_commit_reference, @@ -25,6 +30,11 @@ DomainActivationRevisionV1Alpha2, IntelligenceActivationPlanV1Alpha2, ) +from ace.application.intelligence_builder import IntelligenceBuilderSessionService +from ace.application.intelligence_builder_activation import ( + IntelligenceBuilderActivationError, + IntelligenceBuilderActivationService, +) from ace.core.state import ( GovernedStateCommitReceiptV1, GovernedStateCommitRequestV1, @@ -299,7 +309,7 @@ def _pack_material(): return _encoded(manifest), resources, _encoded(fixture) -def _activation_material(): +def _activation_material(*, product_id="product:activation-fixture"): manifest, resources, fixture = _pack_material() pack = compile_pack_document(manifest, resources) conformance = run_domain_pack_conformance( @@ -331,7 +341,7 @@ def _activation_material(): grant_ref="authority_grant:fixture-read", ) spec = prepare_domain_activation( - product_id="product:activation-fixture", + product_id=product_id, activation_key="fixture", pack=pack, overlay=overlay, @@ -522,6 +532,162 @@ async def test_exact_plan_is_approval_subject_and_restart_receipt_material(): assert lineage.live_authority is False +@pytest.mark.asyncio +async def test_v1alpha2_reload_accepts_only_matching_legacy_material(): + pack, conformance, spec = _activation_material() + _, handoff, watch_admission = await _watch_material() + created = datetime(2026, 8, 11, 12, tzinfo=UTC) + revision = _revision( + plan=_plan( + spec=spec, + action=ActivationPlanAction.INITIAL_ACTIVATION, + created_at=created, + handoff=handoff, + ), + revision=1, + occurred_at=created + timedelta(minutes=1), + ) + store = _MemoryStore() + committed = await DomainActivationPlanAdmissionService(store=store, authority=_Authority()).admit( + revision, + pack=pack, + conformance_receipts=(conformance,), + committed_at=revision.occurred_at + timedelta(seconds=1), + **watch_admission, + ) + key = (DOMAIN_ACTIVATION_PLAN_STATE_KIND, spec.product_id, str(revision.activation_id)) + head = store.heads.pop(key) + envelope = store.revisions[(spec.product_id, str(revision.revision_id))] + store.revisions[(spec.product_id, str(revision.revision_id))] = envelope.model_copy( + update={"state_kind": LEGACY_DOMAIN_ACTIVATION_STATE_KIND} + ) + receipt = committed.commit_receipt + receipt_material = receipt.model_dump(mode="python", exclude={"audit_id", "receipt_id", "receipt_hash"}) + receipt_material["state_kind"] = LEGACY_DOMAIN_ACTIVATION_STATE_KIND + legacy_receipt = type(receipt).model_validate(receipt_material) + del store.receipts[(spec.product_id, receipt.receipt_id)] + store.receipts[(spec.product_id, legacy_receipt.receipt_id)] = legacy_receipt + legacy_head = type(head).model_validate( + { + **head.model_dump(mode="python", exclude={"head_id", "commit_receipt_id", "state_kind"}), + "state_kind": LEGACY_DOMAIN_ACTIVATION_STATE_KIND, + "commit_receipt_id": legacy_receipt.receipt_id, + } + ) + store.heads[(LEGACY_DOMAIN_ACTIVATION_STATE_KIND, spec.product_id, str(revision.activation_id))] = legacy_head + + reopened = await DomainActivationPlanAdmissionService(store=store, authority=_Authority()).reload( + product_id=spec.product_id, + activation_key=spec.activation_key, + ) + assert reopened is not None + assert reopened.commit_receipt.state_kind == LEGACY_DOMAIN_ACTIVATION_STATE_KIND + + store.revisions[(spec.product_id, str(revision.revision_id))] = envelope.model_copy( + update={"state_kind": LEGACY_DOMAIN_ACTIVATION_STATE_KIND, "payload_contract": "wrong.contract/v1"} + ) + with pytest.raises(DomainActivationPlanAdmissionError, match="mixed v1alpha1/v1alpha2"): + await DomainActivationPlanAdmissionService(store=store, authority=_Authority()).reload( + product_id=spec.product_id, + activation_key=spec.activation_key, + ) + + +class _PackResolver: + def __init__(self, pack): + self.pack = pack + + async def load_exact(self, *, reference): + if ( + self.pack.metadata.pack_id == reference.pack_id + and self.pack.metadata.version == reference.pack_version + and self.pack.compiled_pack_id == reference.compiled_pack_id + and self.pack.pack_digest == reference.pack_digest + ): + return self.pack + return None + + +@pytest.mark.asyncio +async def test_builder_bootstrap_coexists_replays_and_revocation_fails_closed(): + watch, handoff, watch_admission = await _watch_material() + product_id = watch.briefing.session.revision.product_id + pack, conformance, spec = _activation_material(product_id=product_id) + created = watch.briefing.session.revision.occurred_at + timedelta(seconds=1) + plan = _plan( + spec=spec, + action=ActivationPlanAction.INITIAL_ACTIVATION, + created_at=created, + handoff=handoff, + ) + revision = _revision(plan=plan, revision=1, occurred_at=created + timedelta(seconds=2)) + authority = _Authority(approved_at=created + timedelta(seconds=1)) + governed = _MemoryStore() + plans = DomainActivationPlanAdmissionService(store=governed, authority=authority) + committed_plan = await plans.admit( + revision, + pack=pack, + conformance_receipts=(conformance,), + committed_at=revision.occurred_at + timedelta(seconds=1), + **watch_admission, + ) + service = IntelligenceBuilderActivationService( + sessions=IntelligenceBuilderSessionService(store=watch.mapped.store), + plans=plans, + compatibility=DomainActivationCompatibilityService(authority=authority), + canonical=DomainActivationAdmissionService(store=governed, authority=authority), + packs=_PackResolver(pack), + ) + recorded = await service.record_current_plan( + product_id=product_id, + session_id=watch.briefing.session.revision.session_id, + committed=committed_plan, + pack=spec.pack, + recorded_at=revision.occurred_at + timedelta(seconds=2), + ) + first = await service.activate( + product_id=product_id, + session_id=recorded.session.revision.session_id, + activation_approval_receipt_ref="approval:canonical-spec", + evaluated_at=revision.occurred_at + timedelta(seconds=3), + ) + replay = await service.activate( + product_id=product_id, + session_id=recorded.session.revision.session_id, + activation_approval_receipt_ref="approval:canonical-spec", + evaluated_at=revision.occurred_at + timedelta(seconds=4), + ) + + assert committed_plan.commit_receipt.state_kind == "domain_activation_plan_v1alpha2" + assert first.binding.commit_receipt.state_kind == "domain_activation_v1alpha1" + assert first.binding.prepared_binding.revision.activation_id == committed_plan.revision.activation_id + assert replay.binding == first.binding + assert replay.receipt_artifact == first.receipt_artifact + assert replay.replayed is True + assert replay.session.revision.stage.value == "active" + + class _Revoked(_Authority): + async def resolve_approval(self, **kwargs): + if kwargs["receipt_ref"] == "approval:canonical-spec": + raise PermissionError("revoked") + return await super().resolve_approval(**kwargs) + + revoked = _Revoked(approved_at=created + timedelta(seconds=1)) + with pytest.raises(IntelligenceBuilderActivationError): + await IntelligenceBuilderActivationService( + sessions=IntelligenceBuilderSessionService(store=watch.mapped.store), + plans=DomainActivationPlanAdmissionService(store=governed, authority=revoked), + compatibility=DomainActivationCompatibilityService(authority=revoked), + canonical=DomainActivationAdmissionService(store=governed, authority=revoked), + packs=_PackResolver(pack), + ).activate( + product_id=product_id, + session_id=recorded.session.revision.session_id, + activation_approval_receipt_ref="approval:canonical-spec", + evaluated_at=revision.occurred_at + timedelta(seconds=5), + ) + + def test_plan_identity_changes_for_effect_or_capability_material_and_drift_fails_closed(): _, _, spec = _activation_material() created = datetime(2026, 8, 11, 12, tzinfo=UTC) @@ -834,7 +1000,7 @@ async def test_mixed_v1alpha1_history_and_stale_conformance_fail_closed(): handoff=handoff, ) revision = _revision(plan=plan, revision=2, occurred_at=start + timedelta(minutes=2)) - with pytest.raises(DomainActivationPlanAdmissionError, match="mixed v1alpha1/v1alpha2"): + with pytest.raises(DomainActivationPlanAdmissionError, match="requires a current v1alpha2 head"): await DomainActivationPlanAdmissionService(store=store, authority=_Authority()).admit( revision, pack=pack, diff --git a/tests/intelligence/test_live_source_ingress.py b/tests/intelligence/test_live_source_ingress.py index 3476f68..88897ec 100644 --- a/tests/intelligence/test_live_source_ingress.py +++ b/tests/intelligence/test_live_source_ingress.py @@ -473,7 +473,7 @@ async def test_each_governed_head_change_during_capture_fails_without_partial_ad def mutate() -> None: env = holder["env"] if race == "activation": - key = next(key for key in env.heads if key[0] == "domain_activation") + key = next(key for key in env.heads if key[0] == env.request.activation_head_precondition.state_kind) env.heads[key] = _next_head(env.heads[key]) elif race == "capability": key = ("capability_state", PRODUCT, CAPABILITY_STATE_ID) diff --git a/tests/intelligence/test_recorded_source_admission.py b/tests/intelligence/test_recorded_source_admission.py index 97e26c0..3148ba8 100644 --- a/tests/intelligence/test_recorded_source_admission.py +++ b/tests/intelligence/test_recorded_source_admission.py @@ -67,7 +67,11 @@ async def _stack(): updated_at=ADMITTED_AT - timedelta(minutes=1), ) activation_head = activation_store.heads[ - ("domain_activation", PRODUCT, str(binding.prepared_binding.revision.activation_id)) + ( + binding.commit_receipt.state_kind, + PRODUCT, + str(binding.prepared_binding.revision.activation_id), + ) ] records = InMemoryImmutableRecordStore( governed_state_heads={