//! Adapter manifest types and built-in manifest registry.

use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;

use crate::{
    AdapterRole, FailureClass, IntegrationMode, LifecycleEventKind, SCHEMA_VERSION, SupportState,
    ValidationError, require_non_empty,
};

/// Manifest placement classes — the trust-neutral, lifecycle-timing
/// vocabulary the adapter manifest uses to declare placement support.
///
/// **Distinct from [`crate::PlacementClass`]**, which is the routing
/// vocabulary the runtime uses on `acceptable_placements` for
/// concrete payload delivery. The manifest declares *capability*;
/// the payload envelope declares *routing intent*. A future
/// revision may unify them; the current contract keeps them
/// separate so manifest evolution does not churn payload routing.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ManifestPlacementClass {
    /// Before any frame opens; e.g. session-init context.
    PreSession,
    /// Leading edge of a frame, before user/task input arrives.
    PreFrameLeading,
    /// Trailing edge of a frame, after input but before model execution.
    PreFrameTrailing,
    /// Inside a tool-result envelope returned to the model.
    ToolResult,
    /// Through an operator or manual surface (skill, command, wrapper).
    ManualOperator,
}

impl ManifestPlacementClass {
    pub const ALL: &'static [Self] = &[
        Self::PreSession,
        Self::PreFrameLeading,
        Self::PreFrameTrailing,
        Self::ToolResult,
        Self::ManualOperator,
    ];
}

/// Per-event capability claim inside a manifest.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ManifestLifecycleEventSupport {
    pub support: SupportState,
    /// Integration modes through which the adapter delivers this event.
    /// May be empty when `support` is `unavailable`.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub modes: Vec<IntegrationMode>,
}

/// Per-placement capability claim inside a manifest.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ManifestPlacementSupport {
    pub support: SupportState,
    /// Placement size limit in bytes when the adapter declares one.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub max_bytes: Option<u64>,
}

/// Capability claim describing how the adapter surfaces
/// `context.pressure_observed` lifecycle evidence.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ManifestContextPressure {
    pub support: SupportState,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub evidence: Option<String>,
}

/// Capability claim describing receipt emission and ledger support.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ManifestReceipts {
    /// Adapter emits its own native receipts.
    pub native: bool,
    /// Lifeloop synthesizes receipts on the adapter's behalf.
    pub lifeloop_synthesized: bool,
    /// Durable cross-invocation receipt ledger.
    pub receipt_ledger: SupportState,
}

/// Per-id support claims for harness identity correlation. Optional
/// on the manifest because a telemetry-only adapter may not expose
/// any of these.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ManifestSessionIdentity {
    pub harness_session_id: SupportState,
    pub harness_run_id: SupportState,
    pub harness_task_id: SupportState,
}

/// Capability claim for the adapter's session-rename surface.
/// Optional on the manifest; absent means "no rename concept."
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ManifestSessionRename {
    pub support: SupportState,
}

/// Capability claim for reset/continuation renewal across a harness
/// boundary. Lifeloop reports whether the adapter can prove the
/// lifecycle path and delivery support; clients own renewal leases,
/// continuation-token policy, and binding to their own state.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ManifestRenewal {
    pub reset: ManifestRenewalReset,
    pub continuation: ManifestRenewalContinuation,
    /// Host integration profile ids required for this renewal path
    /// when the claim is not available through every install shape.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub profiles: Vec<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub evidence: Option<String>,
}

/// Reset-side renewal capability. `native`, `wrapper_mediated`, and
/// `manual` are separate so a manifest can distinguish a real harness
/// reset surface, a launcher/wrapper path, an operator-only path, and
/// the all-`unavailable` "no safe reset path" case.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ManifestRenewalReset {
    pub native: SupportState,
    pub wrapper_mediated: SupportState,
    pub manual: SupportState,
}

/// Continuation-side renewal capability. Observation means the
/// adapter can prove a continuation boundary happened; payload
/// delivery means it can carry client-provided continuation facts
/// across that boundary.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ManifestRenewalContinuation {
    pub observation: SupportState,
    pub payload_delivery: SupportState,
}

/// Capability claim for operator approval/intervention surfaces.
/// Optional; absent means "no operator surface."
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ManifestApprovalSurface {
    pub support: SupportState,
}

/// One telemetry source the adapter exposes for lifecycle evidence.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ManifestTelemetrySource {
    pub source: String,
    pub support: SupportState,
}

/// One pre-declared capability degradation the adapter ships with.
/// Lets a manifest say "this build's `context_pressure` was native
/// upstream but is currently `unavailable` here" without firing a
/// runtime `capability.degraded` event.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ManifestKnownDegradation {
    pub capability: String,
    pub previous_support: SupportState,
    pub current_support: SupportState,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub evidence: Option<String>,
}

/// Adapter manifest. Issue #6 lands the full shape; pre-issue-#6
/// drafts shipped a stub with only schema_version, adapter_id,
/// adapter_version, display_name, roles, integration_modes, and
/// lifecycle_events.
///
/// `contract_version` (this struct's first field) carries the
/// Lifeloop contract version label (e.g. `lifeloop.v0.2`),
/// independent of `adapter_version`. The two are separate so
/// adapters can iterate without bumping the contract.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AdapterManifest {
    pub contract_version: String,
    pub adapter_id: String,
    pub adapter_version: String,
    pub display_name: String,
    pub role: AdapterRole,
    pub integration_modes: Vec<IntegrationMode>,
    pub lifecycle_events: BTreeMap<LifecycleEventKind, ManifestLifecycleEventSupport>,
    pub placement: BTreeMap<ManifestPlacementClass, ManifestPlacementSupport>,
    pub context_pressure: ManifestContextPressure,
    pub receipts: ManifestReceipts,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub session_identity: Option<ManifestSessionIdentity>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub session_rename: Option<ManifestSessionRename>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub renewal: Option<ManifestRenewal>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub approval_surface: Option<ManifestApprovalSurface>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub failure_modes: Vec<FailureClass>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub telemetry_sources: Vec<ManifestTelemetrySource>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub known_degradations: Vec<ManifestKnownDegradation>,
}

impl AdapterManifest {
    pub fn validate(&self) -> Result<(), ValidationError> {
        if self.contract_version != SCHEMA_VERSION {
            return Err(ValidationError::SchemaVersionMismatch {
                expected: SCHEMA_VERSION.to_string(),
                found: self.contract_version.clone(),
            });
        }
        require_non_empty(&self.adapter_id, "manifest.adapter_id")?;
        require_non_empty(&self.adapter_version, "manifest.adapter_version")?;
        require_non_empty(&self.display_name, "manifest.display_name")?;
        if self.integration_modes.is_empty() {
            return Err(ValidationError::InvalidManifest(
                "manifest.integration_modes must declare at least one integration mode".into(),
            ));
        }
        for deg in &self.known_degradations {
            require_non_empty(&deg.capability, "manifest.known_degradations[].capability")?;
        }
        for src in &self.telemetry_sources {
            require_non_empty(&src.source, "manifest.telemetry_sources[].source")?;
        }
        if let Some(renewal) = &self.renewal
            && let Some(evidence) = &renewal.evidence
        {
            require_non_empty(evidence, "manifest.renewal.evidence")?;
        }
        if let Some(renewal) = &self.renewal {
            for profile in &renewal.profiles {
                require_non_empty(profile, "manifest.renewal.profiles[]")?;
            }
        }
        Ok(())
    }
}

// ----------------------------------------------------------------------------
// Manifest registry
// ----------------------------------------------------------------------------

/// Conformance posture of a registered adapter.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ConformanceLevel {
    /// v1-conformance adapter: every capability claim that depends on
    /// extracted code (asset rendering, telemetry, placement) is
    /// paired with a test that verifies the claim.
    V1Conformance,
    /// Initial manifest shipped without full claim verification. The
    /// claims describe expected behavior so clients can negotiate, but
    /// the registry does not yet run capability-claim tests for them.
    PreConformance,
}

/// Registry entry pairing an [`AdapterManifest`] with its
/// [`ConformanceLevel`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RegisteredAdapter {
    pub manifest: AdapterManifest,
    pub conformance: ConformanceLevel,
}

/// Built-in adapter manifest registry. Order is stable so callers
/// that render a `lifeloop adapters` listing get a predictable
/// output without sorting client-side.
pub fn manifest_registry() -> Vec<RegisteredAdapter> {
    vec![
        RegisteredAdapter {
            manifest: codex_manifest(),
            conformance: ConformanceLevel::V1Conformance,
        },
        RegisteredAdapter {
            manifest: claude_manifest(),
            conformance: ConformanceLevel::V1Conformance,
        },
        RegisteredAdapter {
            manifest: hermes_manifest(),
            conformance: ConformanceLevel::PreConformance,
        },
        RegisteredAdapter {
            manifest: openclaw_manifest(),
            conformance: ConformanceLevel::PreConformance,
        },
        RegisteredAdapter {
            manifest: gemini_manifest(),
            conformance: ConformanceLevel::PreConformance,
        },
        RegisteredAdapter {
            manifest: opencode_manifest(),
            conformance: ConformanceLevel::PreConformance,
        },
    ]
}

/// Resolve a registered adapter by `adapter_id`. Returns `None` for
/// unknown ids.
pub fn lookup_manifest(adapter_id: &str) -> Option<RegisteredAdapter> {
    manifest_registry()
        .into_iter()
        .find(|entry| entry.manifest.adapter_id == adapter_id)
}

fn synthesized() -> SupportState {
    SupportState::Synthesized
}

fn native() -> SupportState {
    SupportState::Native
}

fn unavailable() -> SupportState {
    SupportState::Unavailable
}

fn manual() -> SupportState {
    SupportState::Manual
}

/// Codex manifest. Native-hook integration covers Codex's stable hook
/// surface, including `PreCompact` in Codex CLI 0.129+. Capability
/// claims here are paired with verification tests in
/// `tests/manifest_claims.rs`.
pub fn codex_manifest() -> AdapterManifest {
    let lifecycle_events = BTreeMap::from([
        (
            LifecycleEventKind::SessionStarting,
            ManifestLifecycleEventSupport {
                support: native(),
                modes: vec![IntegrationMode::NativeHook],
            },
        ),
        (
            LifecycleEventKind::SessionStarted,
            ManifestLifecycleEventSupport {
                support: native(),
                modes: vec![IntegrationMode::NativeHook],
            },
        ),
        (
            LifecycleEventKind::FrameOpening,
            ManifestLifecycleEventSupport {
                support: native(),
                modes: vec![IntegrationMode::NativeHook],
            },
        ),
        (
            LifecycleEventKind::FrameOpened,
            ManifestLifecycleEventSupport {
                support: synthesized(),
                modes: vec![IntegrationMode::NativeHook],
            },
        ),
        (
            LifecycleEventKind::ContextPressureObserved,
            ManifestLifecycleEventSupport {
                support: native(),
                modes: vec![IntegrationMode::NativeHook],
            },
        ),
        (
            LifecycleEventKind::ContextCompacted,
            ManifestLifecycleEventSupport {
                support: native(),
                modes: vec![IntegrationMode::NativeHook],
            },
        ),
        (
            LifecycleEventKind::FrameEnding,
            ManifestLifecycleEventSupport {
                support: native(),
                modes: vec![IntegrationMode::NativeHook],
            },
        ),
        (
            LifecycleEventKind::FrameEnded,
            ManifestLifecycleEventSupport {
                support: native(),
                modes: vec![IntegrationMode::NativeHook],
            },
        ),
        (
            LifecycleEventKind::SessionEnding,
            ManifestLifecycleEventSupport {
                support: unavailable(),
                modes: Vec::new(),
            },
        ),
        (
            LifecycleEventKind::SessionEnded,
            ManifestLifecycleEventSupport {
                support: unavailable(),
                modes: Vec::new(),
            },
        ),
        (
            LifecycleEventKind::SupervisorTick,
            ManifestLifecycleEventSupport {
                support: unavailable(),
                modes: Vec::new(),
            },
        ),
        (
            LifecycleEventKind::CapabilityDegraded,
            ManifestLifecycleEventSupport {
                support: synthesized(),
                modes: vec![IntegrationMode::NativeHook],
            },
        ),
        (
            LifecycleEventKind::ReceiptEmitted,
            ManifestLifecycleEventSupport {
                support: synthesized(),
                modes: vec![IntegrationMode::NativeHook],
            },
        ),
        (
            LifecycleEventKind::ReceiptGapDetected,
            ManifestLifecycleEventSupport {
                support: unavailable(),
                modes: Vec::new(),
            },
        ),
    ]);

    let placement = BTreeMap::from([
        (
            ManifestPlacementClass::PreSession,
            ManifestPlacementSupport {
                support: native(),
                max_bytes: Some(8192),
            },
        ),
        (
            ManifestPlacementClass::PreFrameLeading,
            ManifestPlacementSupport {
                support: native(),
                max_bytes: Some(8192),
            },
        ),
        (
            ManifestPlacementClass::PreFrameTrailing,
            ManifestPlacementSupport {
                support: unavailable(),
                max_bytes: None,
            },
        ),
        (
            ManifestPlacementClass::ToolResult,
            ManifestPlacementSupport {
                support: unavailable(),
                max_bytes: None,
            },
        ),
        (
            ManifestPlacementClass::ManualOperator,
            ManifestPlacementSupport {
                support: manual(),
                max_bytes: None,
            },
        ),
    ]);

    AdapterManifest {
        contract_version: SCHEMA_VERSION.to_string(),
        adapter_id: "codex".into(),
        adapter_version: "0.1.0".into(),
        display_name: "Codex".into(),
        role: AdapterRole::PrimaryWorker,
        integration_modes: vec![IntegrationMode::NativeHook, IntegrationMode::ManualSkill],
        lifecycle_events,
        placement,
        context_pressure: ManifestContextPressure {
            support: native(),
            evidence: Some(
                "Codex CLI 0.129 exposes PreCompact before context pressure handling and PostCompact after context compacts"
                    .into(),
            ),
        },
        receipts: ManifestReceipts {
            native: false,
            lifeloop_synthesized: true,
            receipt_ledger: unavailable(),
        },
        session_identity: Some(ManifestSessionIdentity {
            harness_session_id: native(),
            harness_run_id: synthesized(),
            harness_task_id: unavailable(),
        }),
        session_rename: None,
        renewal: Some(ManifestRenewal {
            reset: ManifestRenewalReset {
                native: unavailable(),
                wrapper_mediated: synthesized(),
                manual: manual(),
            },
            continuation: ManifestRenewalContinuation {
                observation: native(),
                payload_delivery: synthesized(),
            },
            profiles: vec![crate::host_assets::CCD_RENEWAL_PROFILE.id.into()],
            evidence: Some(
                "Codex Stop block-as-continuation evidence plus subprocess client callback evidence proves wrapper-mediated reset prepare and continuation payload delivery"
                    .into(),
            ),
        }),
        approval_surface: None,
        failure_modes: vec![FailureClass::TransportError, FailureClass::PayloadTooLarge],
        telemetry_sources: Vec::new(),
        known_degradations: Vec::new(),
    }
}

/// Claude manifest. Native-hook integration via `.claude/settings.json`.
pub fn claude_manifest() -> AdapterManifest {
    let lifecycle_events = BTreeMap::from([
        (
            LifecycleEventKind::SessionStarting,
            ManifestLifecycleEventSupport {
                support: native(),
                modes: vec![IntegrationMode::NativeHook],
            },
        ),
        (
            LifecycleEventKind::SessionStarted,
            ManifestLifecycleEventSupport {
                support: native(),
                modes: vec![IntegrationMode::NativeHook],
            },
        ),
        (
            LifecycleEventKind::FrameOpening,
            ManifestLifecycleEventSupport {
                support: native(),
                modes: vec![IntegrationMode::NativeHook],
            },
        ),
        (
            LifecycleEventKind::FrameOpened,
            ManifestLifecycleEventSupport {
                support: native(),
                modes: vec![IntegrationMode::NativeHook],
            },
        ),
        (
            LifecycleEventKind::ContextPressureObserved,
            ManifestLifecycleEventSupport {
                support: native(),
                modes: vec![IntegrationMode::NativeHook],
            },
        ),
        (
            LifecycleEventKind::ContextCompacted,
            ManifestLifecycleEventSupport {
                support: unavailable(),
                modes: Vec::new(),
            },
        ),
        (
            LifecycleEventKind::FrameEnding,
            ManifestLifecycleEventSupport {
                support: native(),
                modes: vec![IntegrationMode::NativeHook],
            },
        ),
        (
            LifecycleEventKind::FrameEnded,
            ManifestLifecycleEventSupport {
                support: native(),
                modes: vec![IntegrationMode::NativeHook],
            },
        ),
        (
            LifecycleEventKind::SessionEnding,
            ManifestLifecycleEventSupport {
                support: native(),
                modes: vec![IntegrationMode::NativeHook],
            },
        ),
        (
            LifecycleEventKind::SessionEnded,
            ManifestLifecycleEventSupport {
                support: native(),
                modes: vec![IntegrationMode::NativeHook],
            },
        ),
        (
            LifecycleEventKind::SupervisorTick,
            ManifestLifecycleEventSupport {
                support: unavailable(),
                modes: Vec::new(),
            },
        ),
        (
            LifecycleEventKind::CapabilityDegraded,
            ManifestLifecycleEventSupport {
                support: synthesized(),
                modes: vec![IntegrationMode::NativeHook],
            },
        ),
        (
            LifecycleEventKind::ReceiptEmitted,
            ManifestLifecycleEventSupport {
                support: synthesized(),
                modes: vec![IntegrationMode::NativeHook],
            },
        ),
        (
            LifecycleEventKind::ReceiptGapDetected,
            ManifestLifecycleEventSupport {
                support: unavailable(),
                modes: Vec::new(),
            },
        ),
    ]);

    let placement = BTreeMap::from([
        (
            ManifestPlacementClass::PreSession,
            ManifestPlacementSupport {
                support: native(),
                max_bytes: Some(16_384),
            },
        ),
        (
            ManifestPlacementClass::PreFrameLeading,
            ManifestPlacementSupport {
                support: native(),
                max_bytes: Some(16_384),
            },
        ),
        (
            ManifestPlacementClass::PreFrameTrailing,
            ManifestPlacementSupport {
                support: unavailable(),
                max_bytes: None,
            },
        ),
        (
            ManifestPlacementClass::ToolResult,
            ManifestPlacementSupport {
                support: unavailable(),
                max_bytes: None,
            },
        ),
        (
            ManifestPlacementClass::ManualOperator,
            ManifestPlacementSupport {
                support: manual(),
                max_bytes: None,
            },
        ),
    ]);

    AdapterManifest {
        contract_version: SCHEMA_VERSION.to_string(),
        adapter_id: "claude".into(),
        adapter_version: "0.1.0".into(),
        display_name: "Claude".into(),
        role: AdapterRole::PrimaryWorker,
        integration_modes: vec![IntegrationMode::NativeHook],
        lifecycle_events,
        placement,
        context_pressure: ManifestContextPressure {
            support: native(),
            evidence: Some(
                "Claude emits PreCompact and SessionEnd events that map directly to context.pressure_observed"
                    .into(),
            ),
        },
        receipts: ManifestReceipts {
            native: false,
            lifeloop_synthesized: true,
            receipt_ledger: unavailable(),
        },
        session_identity: Some(ManifestSessionIdentity {
            harness_session_id: native(),
            harness_run_id: synthesized(),
            harness_task_id: unavailable(),
        }),
        session_rename: None,
        renewal: None,
        approval_surface: None,
        failure_modes: vec![FailureClass::TransportError, FailureClass::PayloadTooLarge],
        telemetry_sources: Vec::new(),
        known_degradations: Vec::new(),
    }
}

/// Hermes pre-conformance manifest. Reference-adapter integration
/// supplied as a JSON descriptor at the path declared in
/// [`crate::host_assets::HERMES_TARGET_ADAPTER`].
pub fn hermes_manifest() -> AdapterManifest {
    pre_conformance_reference_adapter_manifest("hermes", "Hermes")
}

/// OpenClaw pre-conformance manifest.
pub fn openclaw_manifest() -> AdapterManifest {
    pre_conformance_reference_adapter_manifest("openclaw", "OpenClaw")
}

/// Gemini pre-conformance manifest.
pub fn gemini_manifest() -> AdapterManifest {
    pre_conformance_telemetry_only_manifest("gemini", "Gemini")
}

/// OpenCode pre-conformance manifest.
pub fn opencode_manifest() -> AdapterManifest {
    pre_conformance_telemetry_only_manifest("opencode", "OpenCode")
}

fn pre_conformance_reference_adapter_manifest(
    adapter_id: &str,
    display_name: &str,
) -> AdapterManifest {
    let lifecycle_events = BTreeMap::from([
        (
            LifecycleEventKind::SessionStarting,
            ManifestLifecycleEventSupport {
                support: SupportState::Partial,
                modes: vec![IntegrationMode::ReferenceAdapter],
            },
        ),
        (
            LifecycleEventKind::SessionStarted,
            ManifestLifecycleEventSupport {
                support: SupportState::Partial,
                modes: vec![IntegrationMode::ReferenceAdapter],
            },
        ),
        (
            LifecycleEventKind::FrameOpening,
            ManifestLifecycleEventSupport {
                support: SupportState::Partial,
                modes: vec![IntegrationMode::ReferenceAdapter],
            },
        ),
        (
            LifecycleEventKind::FrameEnded,
            ManifestLifecycleEventSupport {
                support: SupportState::Partial,
                modes: vec![IntegrationMode::ReferenceAdapter],
            },
        ),
        (
            LifecycleEventKind::SessionEnded,
            ManifestLifecycleEventSupport {
                support: SupportState::Partial,
                modes: vec![IntegrationMode::ReferenceAdapter],
            },
        ),
    ]);

    let placement = BTreeMap::from([
        (
            ManifestPlacementClass::PreSession,
            ManifestPlacementSupport {
                support: SupportState::Partial,
                max_bytes: None,
            },
        ),
        (
            ManifestPlacementClass::PreFrameLeading,
            ManifestPlacementSupport {
                support: SupportState::Partial,
                max_bytes: None,
            },
        ),
        (
            ManifestPlacementClass::ManualOperator,
            ManifestPlacementSupport {
                support: SupportState::Manual,
                max_bytes: None,
            },
        ),
    ]);

    AdapterManifest {
        contract_version: SCHEMA_VERSION.to_string(),
        adapter_id: adapter_id.to_string(),
        adapter_version: "0.0.1-pre".into(),
        display_name: display_name.to_string(),
        role: AdapterRole::Worker,
        integration_modes: vec![IntegrationMode::ReferenceAdapter],
        lifecycle_events,
        placement,
        context_pressure: ManifestContextPressure {
            support: SupportState::Partial,
            evidence: None,
        },
        receipts: ManifestReceipts {
            native: false,
            lifeloop_synthesized: true,
            receipt_ledger: SupportState::Unavailable,
        },
        session_identity: None,
        session_rename: None,
        renewal: None,
        approval_surface: None,
        failure_modes: Vec::new(),
        telemetry_sources: Vec::new(),
        known_degradations: Vec::new(),
    }
}

fn pre_conformance_telemetry_only_manifest(
    adapter_id: &str,
    display_name: &str,
) -> AdapterManifest {
    let lifecycle_events = BTreeMap::from([
        (
            LifecycleEventKind::SessionStarting,
            ManifestLifecycleEventSupport {
                support: SupportState::Partial,
                modes: vec![IntegrationMode::TelemetryOnly],
            },
        ),
        (
            LifecycleEventKind::ContextPressureObserved,
            ManifestLifecycleEventSupport {
                support: SupportState::Partial,
                modes: vec![IntegrationMode::TelemetryOnly],
            },
        ),
        (
            LifecycleEventKind::SessionEnded,
            ManifestLifecycleEventSupport {
                support: SupportState::Partial,
                modes: vec![IntegrationMode::TelemetryOnly],
            },
        ),
    ]);

    let placement = BTreeMap::from([(
        ManifestPlacementClass::ManualOperator,
        ManifestPlacementSupport {
            support: SupportState::Manual,
            max_bytes: None,
        },
    )]);

    AdapterManifest {
        contract_version: SCHEMA_VERSION.to_string(),
        adapter_id: adapter_id.to_string(),
        adapter_version: "0.0.1-pre".into(),
        display_name: display_name.to_string(),
        role: AdapterRole::Observer,
        integration_modes: vec![IntegrationMode::TelemetryOnly],
        lifecycle_events,
        placement,
        context_pressure: ManifestContextPressure {
            support: SupportState::Partial,
            evidence: None,
        },
        receipts: ManifestReceipts {
            native: false,
            lifeloop_synthesized: true,
            receipt_ledger: SupportState::Unavailable,
        },
        session_identity: None,
        session_rename: None,
        renewal: None,
        approval_surface: None,
        failure_modes: Vec::new(),
        telemetry_sources: Vec::new(),
        known_degradations: Vec::new(),
    }
}
