igerasimov's picture
Deploy Phase 2 dataset classifier (part 19)
58c2da3 verified
Raw History Blame Contribute Delete
12.2 kB
"""Single ordered orchestration shell for the completed blind dataset stages."""
from __future__ import annotations
from typing import Any, Protocol
from pydantic import BaseModel
from gcmd_classifier.datasets.errors import DatasetOrchestrationError
from gcmd_classifier.datasets.models import (
DatasetClassificationOutcome,
DatasetEvidencePacket,
ResolvedCMRCollection,
)
from gcmd_classifier.datasets.orchestration import DatasetRunController, DurableInferenceClient
from gcmd_classifier.datasets.runtime_models import (
ArtifactReference,
ArtifactType,
BlindPipelineTerminal,
CheckpointStatus,
DatasetRuntimeFailure,
PersistedExpertSourceBoundary,
PipelineStage,
TerminalOutcome,
)
from gcmd_classifier.datasets.runtime_persistence import (
DatasetArtifactStore,
canonical_json_bytes,
persist_expert_source_boundary,
verify_persisted_blind_result,
)
from gcmd_classifier.llm.base import ModelClient
class BlindStageServices(Protocol):
"""Adapters call the already implemented stage functions; they add no stage logic."""
def execute(
self,
stage: PipelineStage,
context: dict[PipelineStage, Any],
model_client: ModelClient,
) -> Any:
"""Execute one named stage and return its existing typed stage result."""
_ORDER = (
PipelineStage.CMR_SOURCE,
PipelineStage.IDENTITY,
PipelineStage.BLIND_BOUNDARY,
PipelineStage.README_DISCOVERY,
PipelineStage.README_SELECTION,
PipelineStage.README_RETRIEVAL,
PipelineStage.SOURCE_PERSISTENCE,
PipelineStage.EXTRACTION,
PipelineStage.PRODUCT_RESOLUTION,
PipelineStage.EVIDENCE_SELECTION,
PipelineStage.EVIDENCE_PACKET,
PipelineStage.CLASSIFICATION,
)
_ARTIFACT_TYPE = {
PipelineStage.CMR_SOURCE: ArtifactType.CMR_RESPONSE,
PipelineStage.IDENTITY: ArtifactType.COLLECTION_SNAPSHOT,
PipelineStage.BLIND_BOUNDARY: ArtifactType.COLLECTION_SNAPSHOT,
PipelineStage.README_DISCOVERY: ArtifactType.README_METADATA,
PipelineStage.README_SELECTION: ArtifactType.README_METADATA,
PipelineStage.README_RETRIEVAL: ArtifactType.README_PDF,
PipelineStage.SOURCE_PERSISTENCE: ArtifactType.README_METADATA,
PipelineStage.EXTRACTION: ArtifactType.EXTRACTION_MANIFEST,
PipelineStage.PRODUCT_RESOLUTION: ArtifactType.PRODUCT_RESOLUTION,
PipelineStage.EVIDENCE_SELECTION: ArtifactType.EVIDENCE_SELECTION,
PipelineStage.EVIDENCE_PACKET: ArtifactType.EVIDENCE_PACKET,
PipelineStage.CLASSIFICATION: ArtifactType.BLIND_RESULT,
}
class BlindDatasetPipeline:
"""Execute the approved blind order with durable state and artifact boundaries."""
def __init__(
self,
*,
controller: DatasetRunController,
store: DatasetArtifactStore,
services: BlindStageServices,
model_client: ModelClient,
vocabulary_hash: str,
) -> None:
self.controller = controller
self.store = store
self.services = services
self.model_client = DurableInferenceClient(model_client, controller)
self.vocabulary_hash = vocabulary_hash
self._source_boundary: PersistedExpertSourceBoundary | None = None
def run(self) -> BlindPipelineTerminal:
context: dict[PipelineStage, Any] = {}
references: dict[PipelineStage, ArtifactReference] = {}
try:
for stage in _ORDER:
self.controller.begin(stage, f"Begin {stage.value}.")
value = self.services.execute(stage, context, self.model_client)
if stage is PipelineStage.IDENTITY:
if not isinstance(value, ResolvedCMRCollection):
raise DatasetOrchestrationError(
"EXPERT_SOURCE_BINDING_MISSING",
"Identity stage must return the resolved CMR collection.",
)
self._source_boundary = persist_expert_source_boundary(
store=self.store,
run=self.controller.state.run,
session_id=self.controller.state.session.session_id,
collection=value,
cmr_source_artifact=references[PipelineStage.CMR_SOURCE],
)
context.clear()
context[PipelineStage.CMR_SOURCE] = references[PipelineStage.CMR_SOURCE]
value = self._source_boundary.blind_view
if stage is PipelineStage.README_DISCOVERY and value is None:
self.controller.transition(
stage, CheckpointStatus.SKIPPED, "No README candidate was discovered."
)
return self._terminal(TerminalOutcome.COMPLETED_SKIPPED)
if stage is PipelineStage.SOURCE_PERSISTENCE:
# The stage exists as an explicit service checkpoint; exact source bytes
# were already published at their acquisition boundaries.
reference = references[PipelineStage.README_RETRIEVAL]
else:
reference = self._persist(stage, value, context)
references[stage] = reference
context[stage] = value
checkpoint_artifacts = (reference,)
if stage is PipelineStage.IDENTITY and self._source_boundary is not None:
checkpoint_artifacts = (
reference,
self._source_boundary.cmr_source_artifact,
self._source_boundary.sealed_expert_artifact,
self._source_boundary.expert_source_binding_artifact,
)
self.controller.complete(
stage, f"Completed {stage.value}.", artifacts=checkpoint_artifacts
)
if stage is PipelineStage.CLASSIFICATION:
self.controller.mark_blind_result(produced=True)
self.controller.begin(
PipelineStage.BLIND_RESULT_PERSISTENCE,
"Persist blind result.",
)
self.controller.complete(
PipelineStage.BLIND_RESULT_PERSISTENCE,
"Blind result atomically persisted.",
artifacts=(reference,),
)
self.controller.mark_blind_result(persisted=True)
result = context[PipelineStage.CLASSIFICATION]
packet = self._packet(context[PipelineStage.EVIDENCE_PACKET])
verified = verify_persisted_blind_result(
store=self.store,
run=self.controller.state.run,
session_id=self.controller.state.session.session_id,
reference=references[PipelineStage.CLASSIFICATION],
expected_packet_sha256=packet.packet_sha256,
expected_native_id=result.identity.native_id,
vocabulary_hash=self.vocabulary_hash,
source_boundary=self._required_source_boundary(),
)
self.controller.begin(
PipelineStage.BLIND_RESULT_VERIFICATION, "Verify persisted blind result."
)
self.controller.complete(
PipelineStage.BLIND_RESULT_VERIFICATION,
"Persisted blind-result hash verified.",
artifacts=(verified.artifact,),
)
self.controller.mark_blind_result(verified=True)
terminal = (
TerminalOutcome.COMPLETED_NOT_CLASSIFIED
if result.classification_outcome is DatasetClassificationOutcome.NOT_CLASSIFIED
else TerminalOutcome.COMPLETED_CLASSIFIED
)
return self._terminal(terminal, verified=verified)
except Exception as exc:
attempted = self.controller.state.classification_attempted
failure = DatasetRuntimeFailure(
code=getattr(exc, "code", "BLIND_PIPELINE_FAILED"),
stage=self.controller.state.checkpoints[-1].stage,
message="Blind dataset pipeline stage failed.",
retryable=False,
classification_attempted=attempted,
)
return self._terminal(
TerminalOutcome.POST_INFERENCE_FAILURE
if attempted
else TerminalOutcome.PRE_INFERENCE_FAILURE,
failure=failure,
)
def _required_source_boundary(self) -> PersistedExpertSourceBoundary:
if self._source_boundary is None:
raise DatasetOrchestrationError(
"COMPARISON_PROVENANCE_INCOMPLETE",
"Verified blind results require persisted expert source provenance.",
)
return self._source_boundary
def _persist(
self, stage: PipelineStage, value: Any, context: dict[PipelineStage, Any]
) -> ArtifactReference:
if stage is PipelineStage.CMR_SOURCE and hasattr(value, "response_bytes"):
data = value.response_bytes
elif stage is PipelineStage.README_RETRIEVAL and hasattr(value, "content"):
data = value.content
elif isinstance(value, bytes):
data = value
elif isinstance(value, BaseModel):
data = canonical_json_bytes(value)
elif isinstance(value, dict):
data = canonical_json_bytes(value)
else:
raise DatasetOrchestrationError(
"UNPERSISTABLE_STAGE_RESULT", "Stage did not return a typed persistable value."
)
if stage not in {PipelineStage.CMR_SOURCE} and b'"ScienceKeywords"' in data:
raise DatasetOrchestrationError(
"BLIND_BOUNDARY_VIOLATION", "Expert keywords entered a blind artifact."
)
upstream = tuple(reference.sha256 for reference in self._upstream_references(context))
name = (
"blind_result.json"
if stage is PipelineStage.CLASSIFICATION
else f"artifacts/{stage.value}.json"
)
if stage is PipelineStage.README_RETRIEVAL:
name = "selected_readme.pdf"
return self.store.persist_bytes(
run=self.controller.state.run,
session_id=self.controller.state.session.session_id,
relative_name=name,
data=data,
artifact_type=_ARTIFACT_TYPE[stage],
upstream_sha256=upstream,
)
def _upstream_references(
self, context: dict[PipelineStage, Any]
) -> tuple[ArtifactReference, ...]:
refs: list[ArtifactReference] = []
for checkpoint in self.controller.state.checkpoints:
if checkpoint.status is CheckpointStatus.COMPLETED:
refs.extend(checkpoint.artifact_references)
return tuple(refs)
@staticmethod
def _packet(value: Any) -> DatasetEvidencePacket:
if isinstance(value, DatasetEvidencePacket):
return value
packet = getattr(value, "packet", None)
if isinstance(packet, DatasetEvidencePacket):
return packet
raise DatasetOrchestrationError(
"PACKET_RESULT_INVALID", "Evidence packet stage returned no typed packet."
)
def _terminal(
self,
outcome: TerminalOutcome,
*,
verified: Any = None,
failure: DatasetRuntimeFailure | None = None,
) -> BlindPipelineTerminal:
status = (
CheckpointStatus.SKIPPED
if outcome is TerminalOutcome.COMPLETED_SKIPPED
else CheckpointStatus.FAILED
if failure is not None
else CheckpointStatus.COMPLETED
)
self.controller.transition(
PipelineStage.TERMINAL,
status,
f"Terminal outcome: {outcome.value}.",
terminal_outcome=outcome,
)
return BlindPipelineTerminal(
state=self.controller.state,
terminal_outcome=outcome,
verified_blind_result=verified,
failure=failure,
)