Skip to main content

lodestone_types/
lib.rs

1//! Foundational, dependency-free types shared by Lodestone components.
2
3use std::collections::BTreeMap;
4use std::error::Error;
5use std::fmt::{self, Display, Formatter};
6use std::io::Write;
7use std::path::{Path, PathBuf};
8use std::str::FromStr;
9use std::sync::OnceLock;
10use std::time::{Duration, Instant};
11
12use base64::Engine as _;
13use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
14use serde::de::{self, MapAccess, SeqAccess, Visitor};
15use serde::{Deserialize, Deserializer, Serialize, Serializer};
16
17mod build_identity;
18mod derived_overload;
19mod identity;
20mod lifecycle_completion;
21mod local_development;
22mod mutation_lifecycle;
23mod operation_locator;
24mod performance;
25mod replayable_source;
26mod resource_spec;
27mod shared_root_history;
28mod state_outcomes;
29mod workload_class;
30
31pub use build_identity::*;
32pub use derived_overload::*;
33pub use identity::{
34    BackendAuthorityId, BackendAuthorityPair, ClusterIdentity, ClusterStorageId, CollectionId, CollectionIndexRef,
35    FenceToken, IdentityError, IndexAddress, IndexId, IndexName, NodeIncarnation, ObjectIncarnation, PrimaryTerm,
36    ProtectionSnapshotId, RecoveryAttemptId, RecoveryEra, RecoveryGeneration, ShardKey,
37};
38pub use lifecycle_completion::*;
39pub use local_development::{LocalDevelopmentInstanceId, LocalDevelopmentInstanceIdError};
40pub use mutation_lifecycle::*;
41pub use operation_locator::{
42    ARTIFACT_CHUNK_ALIGNMENT, ARTIFACT_LOCATOR_FORMAT_VERSION, ArtifactLocatorV1, ArtifactReadRangeV1,
43    MAX_ARTIFACT_CHUNK_BYTES, OPERATION_LOCATOR_FORMAT_VERSION, OperationAuthorityKindV1, OperationLocatorError,
44    OperationLocatorV1, VerifiedArtifactChunkV1, verify_artifact_chunk_v1,
45};
46pub use performance::{
47    BoundedStageSnapshotV2, PerformanceObservationV2, PerformanceStageReadingV2, PerformanceStageV2,
48    bounded_stage_snapshot_v2, record_performance_stage_v2,
49};
50pub use replayable_source::*;
51pub use resource_spec::*;
52pub use shared_root_history::*;
53pub use state_outcomes::*;
54pub use workload_class::*;
55
56/// AWS partition is part of an S3 bucket's globally stable provider identity.
57#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
58#[serde(rename_all = "snake_case")]
59pub enum AwsPartition {
60    Aws,
61    AwsCn,
62    AwsUsGov,
63}
64
65/// Provider-observed AWS bucket kind. A directory bucket's AZ ID is durable
66/// identity and must exact-match the writer's provider-observed AZ ID.
67#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
68#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
69pub enum AwsBucketKind {
70    GeneralPurpose,
71    Directory { availability_zone_id: String },
72}
73
74impl AwsBucketKind {
75    #[must_use]
76    pub const fn is_directory(&self) -> bool {
77        matches!(self, Self::Directory { .. })
78    }
79
80    #[must_use]
81    pub fn availability_zone_id(&self) -> Option<&str> {
82        match self {
83            Self::GeneralPurpose => None,
84            Self::Directory { availability_zone_id } => Some(availability_zone_id),
85        }
86    }
87}
88
89macro_rules! semantic_number {
90    ($name:ident, $representation:ty, $documentation:literal) => {
91        #[doc = $documentation]
92        #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
93        #[serde(transparent)]
94        pub struct $name($representation);
95
96        impl $name {
97            /// Constructs the semantic value.
98            #[must_use]
99            pub const fn new(value: $representation) -> Self {
100                Self(value)
101            }
102
103            /// Returns the underlying representation.
104            #[must_use]
105            pub const fn get(self) -> $representation {
106                self.0
107            }
108
109            /// Returns the next value, or `None` at the representation limit.
110            #[must_use]
111            pub const fn checked_next(self) -> Option<Self> {
112                match self.0.checked_add(1) {
113                    Some(value) => Some(Self(value)),
114                    None => None,
115                }
116            }
117        }
118
119        impl Display for $name {
120            fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
121                Display::fmt(&self.0, formatter)
122            }
123        }
124    };
125}
126
127semantic_number!(
128    ShardId,
129    u16,
130    "A semantic identifier for a shard in deterministic models and protocols."
131);
132semantic_number!(Epoch, u32, "A monotonically increasing shard-writer fencing epoch.");
133semantic_number!(
134    SeqNo,
135    u64,
136    "A per-shard monotonically increasing operation sequence number."
137);
138semantic_number!(
139    ManifestGeneration,
140    u64,
141    "A monotonically increasing immutable-manifest generation."
142);
143semantic_number!(
144    SealedRootProofGeneration,
145    u64,
146    "A monotonically increasing sealed-root proof receipt generation. Zero is never issued, so it is the reserved never-proved value."
147);
148semantic_number!(
149    ManifestGraphProofGeneration,
150    u64,
151    "A monotonically increasing post-recovery manifest-graph proof generation. Zero is never issued."
152);
153semantic_number!(
154    RoutingHash,
155    u32,
156    "The stored routing hash used for shard-range selection."
157);
158semantic_number!(
159    IdHash,
160    u128,
161    "The mutable-index document identity hash used by visibility overlays."
162);
163semantic_number!(SegmentId, u128, "A stable immutable segment identifier.");
164semantic_number!(
165    StateIncarnation,
166    u64,
167    "A fenced lifetime identity for one state primary/projection pair."
168);
169/// Immutable identity assigned to one vector field for the lifetime of an
170/// index. Names may be presented by adapters, but sidecars bind this identity.
171#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
172#[serde(transparent)]
173pub struct VectorFieldId(u32);
174
175impl VectorFieldId {
176    /// Constructs a nonzero immutable vector-field identity.
177    pub const fn new(value: u32) -> Result<Self, VectorFieldIdError> {
178        if value == 0 {
179            return Err(VectorFieldIdError);
180        }
181        Ok(Self(value))
182    }
183
184    /// Returns the underlying representation.
185    #[must_use]
186    pub const fn get(self) -> u32 {
187        self.0
188    }
189}
190
191impl Display for VectorFieldId {
192    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
193        Display::fmt(&self.0, formatter)
194    }
195}
196
197/// Refusal to construct the reserved zero vector-field identity.
198#[derive(Clone, Copy, Debug, Eq, PartialEq)]
199pub struct VectorFieldIdError;
200
201impl Display for VectorFieldIdError {
202    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
203        formatter.write_str("vector field identity must be nonzero")
204    }
205}
206
207impl Error for VectorFieldIdError {}
208
209impl<'de> Deserialize<'de> for VectorFieldId {
210    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
211    where
212        D: Deserializer<'de>,
213    {
214        Self::new(u32::deserialize(deserializer)?).map_err(de::Error::custom)
215    }
216}
217/// Upper-half segment identities are reserved for system-derived immutable
218/// maintenance artifacts. Ordinary writer allocation is confined below this
219/// boundary so a published maintenance segment cannot poison `max + 1`.
220pub const RESERVED_SEGMENT_ID_NAMESPACE_START: u128 = 1_u128 << 127;
221
222#[must_use]
223pub const fn is_reserved_segment_id(segment_id: SegmentId) -> bool {
224    segment_id.get() >= RESERVED_SEGMENT_ID_NAMESPACE_START
225}
226
227/// Returns the next ordinary segment identifier while ignoring system-owned
228/// reserved artifacts already present in the same manifest. `None` is the
229/// exact exhaustion result at the namespace boundary.
230pub fn next_ordinary_segment_id<I>(current: I) -> Option<SegmentId>
231where
232    I: IntoIterator<Item = SegmentId>,
233{
234    let maximum = current
235        .into_iter()
236        .filter(|segment_id| !is_reserved_segment_id(*segment_id))
237        .max();
238    let next = maximum.map_or(Some(SegmentId::new(1)), SegmentId::checked_next)?;
239    (!is_reserved_segment_id(next)).then_some(next)
240}
241semantic_number!(
242    ChunkId,
243    u64,
244    "A node-local monotonically increasing WAL chunk identifier."
245);
246semantic_number!(NodeId, u64, "A semantic indexer-node identifier.");
247semantic_number!(
248    ControllerEpoch,
249    u64,
250    "A nonzero monotonically increasing placement-controller claim epoch."
251);
252semantic_number!(
253    NodeLifecycleGeneration,
254    u64,
255    "A monotonically increasing generation of one node lifecycle record."
256);
257semantic_number!(
258    CredentialGeneration,
259    u64,
260    "A monotonically increasing workload-credential binding generation."
261);
262semantic_number!(
263    BootstrapGeneration,
264    u64,
265    "A monotonically increasing irreversible cluster-bootstrap generation."
266);
267semantic_number!(
268    MembershipGeneration,
269    u64,
270    "A monotonically increasing external Raft membership generation."
271);
272semantic_number!(
273    PlacementProgressGeneration,
274    u64,
275    "A monotonically increasing committed placement reconciliation progress generation."
276);
277semantic_number!(
278    RangeTransitionGeneration,
279    u64,
280    "A monotonically increasing online split or merge transition generation."
281);
282semantic_number!(
283    AssignmentGeneration,
284    u64,
285    "A monotonically increasing generation for one exact shard-route assignment."
286);
287semantic_number!(
288    ReadLeaseGeneration,
289    u64,
290    "A monotonically increasing generation for one replicated generation-read lease."
291);
292semantic_number!(
293    SchemaGeneration,
294    u32,
295    "A monotonically increasing mapping/schema generation."
296);
297semantic_number!(
298    PolicyGeneration,
299    u64,
300    "A monotonically increasing authoritative index-policy generation."
301);
302semantic_number!(
303    ResourceRevision,
304    u64,
305    "A monotonically increasing revision of one index resource specification."
306);
307semantic_number!(
308    CrdGeneration,
309    u64,
310    "A nonzero Kubernetes metadata generation for one exact custom-resource incarnation."
311);
312semantic_number!(
313    CatalogManagementGeneration,
314    u64,
315    "A monotonically increasing catalog-owned CAS generation for API/CRD management ownership."
316);
317semantic_number!(
318    IndexRevision,
319    u64,
320    "A monotonically increasing revision of one immutable index incarnation's catalog record."
321);
322semantic_number!(
323    HistoryLedgerRevision,
324    u64,
325    "A monotonically increasing CAS revision of one index's retained-history ledger."
326);
327semantic_number!(
328    AdmissionGeneration,
329    u64,
330    "A monotonically increasing catalog admission fence for one index incarnation."
331);
332semantic_number!(
333    RoutingRevision,
334    u64,
335    "A monotonically increasing revision of one index's complete routing partition."
336);
337semantic_number!(
338    MemtableGeneration,
339    u64,
340    "A node-local active or frozen memtable generation."
341);
342semantic_number!(
343    BackendAuthority,
344    u64,
345    "A stable scheduler partition for one authoritative durability backend."
346);
347semantic_number!(
348    ClusterRevision,
349    u64,
350    "A monotonically increasing revision of the replicated cluster view."
351);
352semantic_number!(
353    RootRevision,
354    u64,
355    "A monotonically increasing revision of the authoritative durable root set."
356);
357semantic_number!(
358    LogicalMillis,
359    u64,
360    "Metastore-owned logical milliseconds used for leases without wall-clock dependence."
361);
362semantic_number!(
363    TaskCheckpointGeneration,
364    u64,
365    "A monotonically increasing CAS generation for one durable task checkpoint."
366);
367semantic_number!(
368    AdministrationRevision,
369    u64,
370    "A monotonically increasing revision of the collection-scoped administration catalog."
371);
372semantic_number!(
373    AliasRevision,
374    u64,
375    "A monotonically increasing revision of one exact alias record."
376);
377semantic_number!(
378    ComponentTemplateRevision,
379    u64,
380    "A monotonically increasing revision of one component-template incarnation."
381);
382semantic_number!(
383    IndexTemplateRevision,
384    u64,
385    "A monotonically increasing revision of one index-template incarnation."
386);
387semantic_number!(
388    IngestPipelineRevision,
389    u64,
390    "A monotonically increasing revision of one catalog-owned ingest pipeline."
391);
392semantic_number!(
393    DataStreamRevision,
394    u64,
395    "A monotonically increasing revision of one data-stream incarnation."
396);
397semantic_number!(
398    LifecyclePolicyRevision,
399    u64,
400    "A monotonically increasing revision of one lifecycle-policy incarnation."
401);
402semantic_number!(
403    SecurityPolicyRevision,
404    u64,
405    "A monotonically increasing revision of the sole coordinated security-policy snapshot."
406);
407semantic_number!(
408    CollectionSecurityGeneration,
409    u64,
410    "A monotonically increasing authorization generation for one Collection's public-security records."
411);
412semantic_number!(
413    QuotaPolicyGeneration,
414    u64,
415    "A monotonically increasing quota-policy generation scoped to one Collection."
416);
417semantic_number!(
418    SecurityRoleGeneration,
419    u64,
420    "A monotonically increasing generation of one Collection-local security role."
421);
422semantic_number!(
423    SecurityPrincipalGeneration,
424    u64,
425    "A monotonically increasing generation of one Collection-local security principal."
426);
427semantic_number!(
428    SecurityDirectSequence,
429    u64,
430    "A monotonically increasing position in the coordinated security direct-command replay journal."
431);
432semantic_number!(
433    ApiKeyGeneration,
434    u64,
435    "A monotonically increasing generation of one API-key verifier record."
436);
437
438pub const AUTHORIZATION_BINDING_FORMAT_VERSION: u16 = 1;
439
440/// Exact Collection-local authority used by one authenticated public request.
441///
442/// `ordering_revision` and `public_security_root_digest` retain the global
443/// provenance of the decision. Callers must use [`Self::same_local_authority`]
444/// for ordinary authorization equality so an unrelated Collection mutation
445/// cannot invalidate admitted work.
446#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
447#[serde(deny_unknown_fields)]
448pub struct AuthorizationBindingV1 {
449    pub format_version: u16,
450    pub recovery_era: RecoveryEra,
451    pub public_security_root_digest: [u8; 32],
452    pub ordering_revision: SecurityPolicyRevision,
453    pub collection_id: CollectionId,
454    pub collection_security_generation: CollectionSecurityGeneration,
455    pub principal_id: PrincipalId,
456    pub principal_generation: SecurityPrincipalGeneration,
457    pub api_key_id: ApiKeyId,
458    pub api_key_generation: ApiKeyGeneration,
459    pub role_generations_digest: [u8; 32],
460    pub quota_policy_generation: QuotaPolicyGeneration,
461    pub authorized_scope_digest: [u8; 32],
462}
463
464impl AuthorizationBindingV1 {
465    #[must_use]
466    pub fn is_valid(self) -> bool {
467        self.format_version == AUTHORIZATION_BINDING_FORMAT_VERSION
468            && self.public_security_root_digest != [0; 32]
469            && self.ordering_revision.get() != 0
470            && self.collection_security_generation.get() != 0
471            && self.principal_id.get() != 0
472            && self.principal_generation.get() != 0
473            && self.api_key_id.get() != 0
474            && self.api_key_generation.get() != 0
475            && self.role_generations_digest != [0; 32]
476            && self.quota_policy_generation.get() != 0
477            && self.authorized_scope_digest != [0; 32]
478    }
479
480    /// Compares only fences that can change the authority of this exact
481    /// Collection/principal/key/scope path. Global root and ordering movement
482    /// remain provenance and deliberately do not participate.
483    #[must_use]
484    pub fn same_local_authority(self, other: Self) -> bool {
485        self.same_generation_authority(other)
486            && self.role_generations_digest == other.role_generations_digest
487            && self.authorized_scope_digest == other.authorized_scope_digest
488    }
489
490    /// Compares the local authorization revocation and mutation fences while
491    /// permitting a caller to narrow or re-evaluate the admitted action/index
492    /// scope. Quota owners compare `quota_policy_generation` separately; a
493    /// quota-only change must not revoke authorization or queued resource work.
494    #[must_use]
495    pub fn same_generation_authority(self, other: Self) -> bool {
496        self.is_valid()
497            && other.is_valid()
498            && self.format_version == other.format_version
499            && self.recovery_era == other.recovery_era
500            && self.collection_id == other.collection_id
501            && self.collection_security_generation == other.collection_security_generation
502            && self.principal_id == other.principal_id
503            && self.principal_generation == other.principal_generation
504            && self.api_key_id == other.api_key_id
505            && self.api_key_generation == other.api_key_generation
506    }
507}
508semantic_number!(
509    AuditSequence,
510    u64,
511    "A recovery-backed sequence in the tamper-evident security audit chain."
512);
513semantic_number!(
514    AuditRangeId,
515    u64,
516    "A recovery-backed identity for one fenced Gateway audit sequence range."
517);
518semantic_number!(
519    QuotaGrantId,
520    u64,
521    "A recovery-backed identity for one aggregate Gateway quota grant."
522);
523semantic_number!(
524    QuotaWindow,
525    u64,
526    "A coordinated logical quota-window identity that cannot be supplied by an adapter."
527);
528semantic_number!(
529    ProcessMonotonicMillis,
530    u64,
531    "Process-monotonic milliseconds used only for local freshness, timeout, and lifetime bounds."
532);
533semantic_number!(
534    UnixMillis,
535    u64,
536    "Unix milliseconds used only for certificate validity observations."
537);
538
539/// Nonzero process-lifetime identity for one exact Gateway incarnation.
540///
541/// This is separate from `NodeToken`: embedded all-role development
542/// composition still needs an unforgeable process fence, while distributed
543/// Gateway ownership additionally carries the exact `NodeToken`.
544#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
545#[serde(transparent)]
546pub struct GatewayIncarnation([u8; 32]);
547
548impl GatewayIncarnation {
549    pub fn new(value: [u8; 32]) -> Result<Self, IdentityError> {
550        if value == [0; 32] {
551            return Err(IdentityError::Zero {
552                kind: "GatewayIncarnation",
553            });
554        }
555        Ok(Self(value))
556    }
557
558    #[must_use]
559    pub const fn get(self) -> [u8; 32] {
560        self.0
561    }
562
563    #[must_use]
564    pub const fn as_bytes(&self) -> &[u8; 32] {
565        &self.0
566    }
567}
568
569impl<'de> Deserialize<'de> for GatewayIncarnation {
570    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
571    where
572        D: Deserializer<'de>,
573    {
574        let value = <[u8; 32]>::deserialize(deserializer)?;
575        Self::new(value).map_err(de::Error::custom)
576    }
577}
578
579/// Exact TLS negotiation profile owned by one public listener.
580#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
581#[serde(rename_all = "snake_case")]
582pub enum PublicTlsProfileV1 {
583    Http,
584    Grpc,
585    Native,
586    Resp,
587}
588
589/// One intentional public Gateway protocol edge.
590#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
591#[serde(rename_all = "snake_case")]
592pub enum PublicGatewayListenerV1 {
593    #[serde(rename = "api")]
594    LodestoneApi,
595    ElasticsearchHttp,
596    OtlpHttp,
597    JaegerHttp,
598    OtlpGrpc,
599    SubscriptionGrpc,
600    SubscriptionWebSocket,
601    Resp,
602}
603
604impl PublicGatewayListenerV1 {
605    #[must_use]
606    pub const fn required_tls_profile(self) -> PublicTlsProfileV1 {
607        match self {
608            Self::LodestoneApi => PublicTlsProfileV1::Native,
609            Self::ElasticsearchHttp | Self::OtlpHttp | Self::JaegerHttp | Self::SubscriptionWebSocket => {
610                PublicTlsProfileV1::Http
611            }
612            Self::OtlpGrpc | Self::SubscriptionGrpc => PublicTlsProfileV1::Grpc,
613            Self::Resp => PublicTlsProfileV1::Resp,
614        }
615    }
616}
617
618/// One immutable production workload role. A distributed identity owns
619/// exactly one role; `roles=all` is a development composition and never a
620/// value of this enum.
621#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
622#[serde(rename_all = "snake_case")]
623pub enum NodeRole {
624    MetastoreVoter,
625    Placement,
626    Orchestrator,
627    Gateway,
628    Indexer,
629    Searcher,
630    Compactor,
631}
632
633/// The exact current incarnation of one durable node identity.
634#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
635#[serde(deny_unknown_fields)]
636pub struct NodeToken {
637    pub recovery_era: RecoveryEra,
638    pub node_id: NodeId,
639    pub node_incarnation: NodeIncarnation,
640}
641
642/// Domain-separated identity of one Kubernetes object incarnation.
643///
644/// A digest follows neither a human name nor a delete/recreate. The caller
645/// derives it from the Kubernetes cluster UID, namespace, kind, and object
646/// UID under the P7 domain.
647#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
648#[serde(transparent)]
649pub struct KubernetesObjectUidDigest([u8; 32]);
650
651impl KubernetesObjectUidDigest {
652    pub fn new(value: [u8; 32]) -> Result<Self, IdentityError> {
653        if value == [0; 32] {
654            return Err(IdentityError::Zero {
655                kind: "KubernetesObjectUidDigest",
656            });
657        }
658        Ok(Self(value))
659    }
660
661    #[must_use]
662    pub const fn get(self) -> [u8; 32] {
663        self.0
664    }
665
666    #[must_use]
667    pub const fn as_bytes(&self) -> &[u8; 32] {
668        &self.0
669    }
670}
671
672impl<'de> Deserialize<'de> for KubernetesObjectUidDigest {
673    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
674    where
675        D: Deserializer<'de>,
676    {
677        let value = <[u8; 32]>::deserialize(deserializer)?;
678        Self::new(value).map_err(de::Error::custom)
679    }
680}
681
682impl NodeToken {
683    pub fn new(
684        recovery_era: RecoveryEra,
685        node_id: NodeId,
686        node_incarnation: NodeIncarnation,
687    ) -> Result<Self, IdentityError> {
688        if node_id.get() == 0 {
689            return Err(IdentityError::Zero { kind: "NodeId" });
690        }
691        Ok(Self {
692            recovery_era,
693            node_id,
694            node_incarnation,
695        })
696    }
697}
698
699/// Complete fence for placement-controller-owned mutations.
700#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
701#[serde(deny_unknown_fields)]
702pub struct ControllerToken {
703    pub recovery_era: RecoveryEra,
704    pub controller_epoch: ControllerEpoch,
705}
706
707impl ControllerToken {
708    pub fn new(recovery_era: RecoveryEra, controller_epoch: ControllerEpoch) -> Result<Self, IdentityError> {
709        if controller_epoch.get() == 0 {
710            return Err(IdentityError::Zero {
711                kind: "ControllerEpoch",
712            });
713        }
714        Ok(Self {
715            recovery_era,
716            controller_epoch,
717        })
718    }
719}
720
721/// A stable identifier for a durable background task.
722///
723/// JSON encodes this as a decimal string because JSON numbers and
724/// `serde_json::Value` cannot losslessly carry the complete `u128` domain.
725#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
726pub struct TaskId(u128);
727
728impl TaskId {
729    #[must_use]
730    pub const fn new(value: u128) -> Self {
731        Self(value)
732    }
733
734    #[must_use]
735    pub const fn get(self) -> u128 {
736        self.0
737    }
738
739    #[must_use]
740    pub const fn checked_next(self) -> Option<Self> {
741        match self.0.checked_add(1) {
742            Some(value) => Some(Self(value)),
743            None => None,
744        }
745    }
746}
747
748impl Display for TaskId {
749    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
750        Display::fmt(&self.0, formatter)
751    }
752}
753
754impl Serialize for TaskId {
755    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
756    where
757        S: Serializer,
758    {
759        serializer.collect_str(self)
760    }
761}
762
763impl<'de> Deserialize<'de> for TaskId {
764    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
765    where
766        D: Deserializer<'de>,
767    {
768        struct TaskIdVisitor;
769
770        impl Visitor<'_> for TaskIdVisitor {
771            type Value = TaskId;
772
773            fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
774                formatter.write_str("a decimal task-id string")
775            }
776
777            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
778            where
779                E: de::Error,
780            {
781                value
782                    .parse::<u128>()
783                    .map(TaskId)
784                    .map_err(|_| E::custom("task id is outside the u128 domain"))
785            }
786        }
787
788        deserializer.deserialize_str(TaskIdVisitor)
789    }
790}
791
792macro_rules! semantic_decimal_identity {
793    ($name:ident, $documentation:literal) => {
794        #[doc = $documentation]
795        #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
796        #[serde(transparent)]
797        pub struct $name(TaskId);
798
799        impl $name {
800            /// Constructs the semantic identity.
801            #[must_use]
802            pub const fn new(value: u128) -> Self {
803                Self(TaskId::new(value))
804            }
805
806            /// Returns the underlying recovery-backed allocation value.
807            #[must_use]
808            pub const fn get(self) -> u128 {
809                self.0.get()
810            }
811        }
812
813        impl Display for $name {
814            fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
815                Display::fmt(&self.0, formatter)
816            }
817        }
818    };
819}
820
821semantic_decimal_identity!(
822    ComponentTemplateId,
823    "A recovery-backed identity for one component-template incarnation."
824);
825semantic_decimal_identity!(
826    IndexTemplateId,
827    "A recovery-backed identity for one index-template incarnation."
828);
829semantic_decimal_identity!(
830    IngestPipelineId,
831    "A recovery-backed identity for one ingest-pipeline incarnation."
832);
833semantic_decimal_identity!(
834    DataStreamId,
835    "A recovery-backed identity for one data-stream incarnation."
836);
837semantic_decimal_identity!(
838    LifecyclePolicyId,
839    "A recovery-backed identity for one lifecycle-policy incarnation."
840);
841semantic_decimal_identity!(
842    ExternalKeyResolverCapabilityId,
843    "An opaque capability identity authorizing one external content-key resolver class."
844);
845semantic_decimal_identity!(
846    PrincipalId,
847    "A recovery-backed, never-reused security-principal identity."
848);
849semantic_decimal_identity!(ApiKeyId, "A recovery-backed, never-reused API-key identity.");
850semantic_decimal_identity!(BackupId, "A recovery-backed, never-reused portable-backup identity.");
851
852/// Stable idempotency identity of one administration action.
853#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
854#[serde(transparent)]
855pub struct AdministrationActionId([u8; 32]);
856
857impl AdministrationActionId {
858    /// Constructs a nonzero action identity.
859    pub fn new(value: [u8; 32]) -> Result<Self, IdentityError> {
860        if value == [0; 32] {
861            return Err(IdentityError::Zero {
862                kind: "AdministrationActionId",
863            });
864        }
865        Ok(Self(value))
866    }
867
868    /// Returns the stable action bytes.
869    #[must_use]
870    pub const fn get(self) -> [u8; 32] {
871        self.0
872    }
873
874    /// Borrows the stable action bytes.
875    #[must_use]
876    pub const fn as_bytes(&self) -> &[u8; 32] {
877        &self.0
878    }
879}
880
881impl<'de> Deserialize<'de> for AdministrationActionId {
882    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
883    where
884        D: Deserializer<'de>,
885    {
886        let value = <[u8; 32]>::deserialize(deserializer)?;
887        Self::new(value).map_err(de::Error::custom)
888    }
889}
890
891/// Stable idempotency identity of one security-policy or audit action.
892#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
893#[serde(transparent)]
894pub struct SecurityActionId([u8; 32]);
895
896impl SecurityActionId {
897    /// Constructs a nonzero action identity.
898    pub fn new(value: [u8; 32]) -> Result<Self, IdentityError> {
899        if value == [0; 32] {
900            return Err(IdentityError::Zero {
901                kind: "SecurityActionId",
902            });
903        }
904        Ok(Self(value))
905    }
906
907    /// Returns the stable action bytes.
908    #[must_use]
909    pub const fn get(self) -> [u8; 32] {
910        self.0
911    }
912
913    /// Borrows the stable action bytes.
914    #[must_use]
915    pub const fn as_bytes(&self) -> &[u8; 32] {
916        &self.0
917    }
918}
919
920impl<'de> Deserialize<'de> for SecurityActionId {
921    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
922    where
923        D: Deserializer<'de>,
924    {
925        let value = <[u8; 32]>::deserialize(deserializer)?;
926        Self::new(value).map_err(de::Error::custom)
927    }
928}
929
930/// Stable idempotency identity of one portable-backup lifecycle action.
931#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
932#[serde(transparent)]
933pub struct BackupActionId([u8; 32]);
934
935impl BackupActionId {
936    /// Constructs a nonzero action identity.
937    pub fn new(value: [u8; 32]) -> Result<Self, IdentityError> {
938        if value == [0; 32] {
939            return Err(IdentityError::Zero { kind: "BackupActionId" });
940        }
941        Ok(Self(value))
942    }
943
944    /// Returns the stable action bytes.
945    #[must_use]
946    pub const fn get(self) -> [u8; 32] {
947        self.0
948    }
949
950    /// Borrows the stable action bytes.
951    #[must_use]
952    pub const fn as_bytes(&self) -> &[u8; 32] {
953        &self.0
954    }
955}
956
957impl<'de> Deserialize<'de> for BackupActionId {
958    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
959    where
960        D: Deserializer<'de>,
961    {
962        let value = <[u8; 32]>::deserialize(deserializer)?;
963        Self::new(value).map_err(de::Error::custom)
964    }
965}
966
967/// The exclusive upper bound of a shard routing-hash range.
968///
969/// This is a `u64` because the final range ends at `2^32`, one past the
970/// largest value representable by [`RoutingHash`].
971#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
972#[serde(transparent)]
973pub struct RoutingRangeEnd(u64);
974
975impl RoutingRangeEnd {
976    /// The exclusive upper bound of the complete u32 routing space.
977    pub const FULL: Self = Self(u32::MAX as u64 + 1);
978
979    /// Constructs a checked exclusive routing-range bound.
980    pub fn new(value: u64) -> Result<Self, RoutingRangeError> {
981        if value == 0 || value > Self::FULL.0 {
982            Err(RoutingRangeError::InvalidEnd(value))
983        } else {
984            Ok(Self(value))
985        }
986    }
987
988    /// Returns the underlying exclusive bound.
989    #[must_use]
990    pub const fn get(self) -> u64 {
991        self.0
992    }
993}
994
995/// A distribution-ready shard identity and its current fencing epoch.
996#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
997pub struct ShardDescriptor {
998    pub shard_id: ShardId,
999    pub epoch: Epoch,
1000    pub hash_start: RoutingHash,
1001    pub hash_end: RoutingRangeEnd,
1002}
1003
1004impl ShardDescriptor {
1005    /// Constructs a non-empty hash-range descriptor.
1006    pub fn new(
1007        shard_id: ShardId,
1008        epoch: Epoch,
1009        hash_start: RoutingHash,
1010        hash_end: RoutingRangeEnd,
1011    ) -> Result<Self, RoutingRangeError> {
1012        if u64::from(hash_start.get()) >= hash_end.get() {
1013            return Err(RoutingRangeError::EmptyOrReversed {
1014                start: hash_start,
1015                end: hash_end,
1016            });
1017        }
1018        Ok(Self {
1019            shard_id,
1020            epoch,
1021            hash_start,
1022            hash_end,
1023        })
1024    }
1025
1026    /// Returns whether the stored routing hash belongs to this descriptor.
1027    #[must_use]
1028    pub const fn contains(self, routing_hash: RoutingHash) -> bool {
1029        let value = routing_hash.get() as u64;
1030        value >= self.hash_start.get() as u64 && value < self.hash_end.get()
1031    }
1032}
1033
1034/// Invalid routing-range metadata.
1035#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1036pub enum RoutingRangeError {
1037    InvalidEnd(u64),
1038    EmptyOrReversed { start: RoutingHash, end: RoutingRangeEnd },
1039}
1040
1041impl Display for RoutingRangeError {
1042    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
1043        match self {
1044            Self::InvalidEnd(end) => write!(
1045                formatter,
1046                "routing range end {end} is outside 1..={}",
1047                RoutingRangeEnd::FULL.get()
1048            ),
1049            Self::EmptyOrReversed { start, end } => write!(
1050                formatter,
1051                "routing range start {start} must be below exclusive end {}",
1052                end.get()
1053            ),
1054        }
1055    }
1056}
1057
1058impl Error for RoutingRangeError {}
1059
1060/// Versioned durability policy identity.
1061///
1062/// The policy names describe behavior rather than one provider product.
1063/// `ZonalLowLatency` is admitted only when the selected object leaf proves the
1064/// required zonal semantics. The remaining stable names are deliberate
1065/// reservations and return a typed unavailable refusal.
1066#[non_exhaustive]
1067#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
1068#[serde(rename_all = "snake_case")]
1069pub enum DurabilityClass {
1070    RegionalObject,
1071    ZonalLowLatency,
1072    Relaxed,
1073    Ephemeral,
1074}
1075
1076impl DurabilityClass {
1077    /// Returns the stable wire name.
1078    #[must_use]
1079    pub const fn as_str(self) -> &'static str {
1080        match self {
1081            Self::RegionalObject => "regional_object",
1082            Self::ZonalLowLatency => "zonal_low_latency",
1083            Self::Relaxed => "relaxed",
1084            Self::Ephemeral => "ephemeral",
1085        }
1086    }
1087
1088    /// Refuses every durability class without an admitted execution protocol.
1089    pub const fn require_available(self) -> Result<(), DurabilityClassUnavailable> {
1090        match self {
1091            Self::RegionalObject | Self::ZonalLowLatency => Ok(()),
1092            Self::Relaxed | Self::Ephemeral => Err(DurabilityClassUnavailable { class: self }),
1093        }
1094    }
1095
1096    /// Refuses classes without an admitted state-index acknowledgement model.
1097    ///
1098    /// P12.0 admits `ephemeral` at a whole-incarnation memory boundary, but
1099    /// does not make it reachable through the ordinary registered-WAL writer.
1100    pub const fn require_state_available(self) -> Result<(), DurabilityClassUnavailable> {
1101        match self {
1102            Self::RegionalObject | Self::ZonalLowLatency | Self::Ephemeral => Ok(()),
1103            Self::Relaxed => Err(DurabilityClassUnavailable { class: self }),
1104        }
1105    }
1106}
1107
1108impl Display for DurabilityClass {
1109    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
1110        formatter.write_str(self.as_str())
1111    }
1112}
1113
1114/// A recognized durability policy that has no admitted execution backend.
1115#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1116pub struct DurabilityClassUnavailable {
1117    pub class: DurabilityClass,
1118}
1119
1120impl Display for DurabilityClassUnavailable {
1121    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
1122        write!(
1123            formatter,
1124            "durability class '{}' is recognized but unavailable",
1125            self.class
1126        )
1127    }
1128}
1129
1130impl Error for DurabilityClassUnavailable {}
1131
1132/// Client response visibility independent of durability.
1133#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)]
1134#[serde(rename_all = "snake_case")]
1135pub enum VisibilityMode {
1136    #[default]
1137    Applied,
1138    WaitFor,
1139}
1140
1141/// The durable acknowledgement boundary actually achieved.
1142#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1143#[serde(rename_all = "snake_case")]
1144pub enum DurabilityBoundary {
1145    Registered,
1146    /// P12 state is visible only inside the current fenced RAM incarnation.
1147    IncarnationMemory,
1148}
1149
1150/// Maximum failure loss promised by a successful acknowledgement.
1151#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1152#[serde(rename_all = "snake_case", tag = "kind")]
1153pub enum DurabilityLoss {
1154    Zero,
1155    WholeIncarnation,
1156}
1157
1158/// Observable write-pipeline progress.
1159#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
1160#[serde(rename_all = "snake_case")]
1161pub enum WriteStage {
1162    Validated,
1163    Admitted,
1164    Sequenced,
1165    ObjectPut,
1166    Registered,
1167    AppliedToActiveMemtable,
1168    MicroCommitted,
1169    Sealed,
1170}
1171
1172/// Per-index mutation mode.
1173#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1174#[serde(rename_all = "snake_case")]
1175pub enum IndexMode {
1176    Mutable,
1177    AppendLog,
1178    /// Primary truth is a fenced, fully resident state table; Tantivy is an
1179    /// asynchronously derived mutable projection.
1180    State,
1181}
1182
1183impl IndexMode {
1184    /// Whether indexed versions require identity replacement and tombstones.
1185    #[must_use]
1186    pub const fn has_mutation_visibility(self) -> bool {
1187        matches!(self, Self::Mutable | Self::State)
1188    }
1189
1190    #[must_use]
1191    pub const fn is_state(self) -> bool {
1192        matches!(self, Self::State)
1193    }
1194}
1195
1196/// Current version of the normalized semantic mapping model shared by the
1197/// catalog and Tantivy compiler. Format 7 adds a distinct bounded vector-set
1198/// field for exact late-interaction scoring.
1199pub const MAPPING_FORMAT_VERSION: u16 = 7;
1200
1201/// Maximum fields admitted by one normalized mapping.
1202pub const MAX_MAPPING_FIELDS: usize = 16_384;
1203
1204/// Version of the vector mapping extension embedded in the catalog mapping.
1205pub const VECTOR_MAPPING_FORMAT_VERSION: u16 = 2;
1206
1207/// Version of the sparse-vector mapping extension embedded in the catalog.
1208pub const SPARSE_VECTOR_MAPPING_FORMAT_VERSION: u16 = 1;
1209
1210/// Version of the vector-set mapping extension embedded in the catalog.
1211pub const VECTOR_SET_MAPPING_FORMAT_VERSION: u16 = 1;
1212
1213/// Maximum vector fields admitted by one mapping generation.
1214pub const MAX_VECTOR_MAPPING_FIELDS: usize = 8;
1215
1216/// Hard logical dimension ceiling shared by mappings, source values, queries,
1217/// codecs, and generated protocol projections.
1218pub const MAX_VECTOR_DIMENSIONS: u16 = 16_384;
1219
1220/// Maximum encoded payload admitted for one dense value.
1221pub const MAX_VECTOR_BYTES_PER_FIELD: usize = 64 * 1_024;
1222
1223/// Maximum encoded dense-vector payload admitted for one document before WAL.
1224pub const MAX_VECTOR_BYTES_PER_DOCUMENT: usize = 64 * 1_024;
1225
1226/// Maximum encoded dense-vector payload admitted for one query branch.
1227pub const MAX_VECTOR_BYTES_PER_QUERY: usize = 64 * 1_024;
1228
1229/// Maximum aggregate encoded dense-vector payload admitted in one request.
1230pub const MAX_VECTOR_BYTES_PER_REQUEST: usize = 16 * MAX_VECTOR_BYTES_PER_QUERY;
1231
1232/// Maximum numeric sparse dimension admitted by mappings and values.
1233pub const MAX_SPARSE_VECTOR_DIMENSION: u32 = 16_777_215;
1234
1235/// Maximum non-zero entries admitted for one sparse field or query.
1236pub const MAX_SPARSE_VECTOR_NONZERO: usize = 4_096;
1237
1238/// Maximum encoded sparse payload admitted for one field or query. Each
1239/// entry owns one u32 dimension and one mapping-selected weight.
1240pub const MAX_SPARSE_VECTOR_BYTES: usize = 32 * 1_024;
1241
1242/// Maximum token vectors admitted for one document vector-set field.
1243pub const MAX_VECTOR_SET_DOCUMENT_VECTORS: usize = 128;
1244
1245/// Maximum token vectors admitted for one late-interaction query branch.
1246pub const MAX_VECTOR_SET_QUERY_VECTORS: usize = 32;
1247
1248/// Maximum canonical element bytes admitted for one document vector set.
1249pub const MAX_VECTOR_SET_BYTES_PER_FIELD: usize = 64 * 1_024;
1250
1251/// Maximum canonical element bytes admitted for one query vector set.
1252pub const MAX_VECTOR_SET_BYTES_PER_QUERY: usize = 64 * 1_024;
1253
1254/// Maximum vector records retained by one mutable fresh-generation overlay.
1255pub const MAX_FRESH_VECTOR_RECORDS: usize = 16_384;
1256
1257/// Maximum decoded vector bytes retained by one mutable fresh-generation
1258/// overlay. This is deliberately independent of one document's byte ceiling.
1259pub const MAX_FRESH_VECTOR_BYTES: usize = 128 * 1_024 * 1_024;
1260
1261/// Absolute ceiling for one immutable-vector build's reconstructible scratch.
1262/// Operators may and normally do assign a smaller resource budget.
1263pub const MAX_VECTOR_BUILD_SCRATCH_BYTES: u64 = 16 * 1_024 * 1_024 * 1_024;
1264
1265/// Absolute ceiling for one process's decoded immutable-vector cache. The
1266/// runtime resource specification remains the smaller operational authority.
1267pub const MAX_VECTOR_DECODED_CACHE_BYTES: u64 = 16 * 1_024 * 1_024 * 1_024;
1268
1269/// Hard logical ceiling for vector bytes returned as explicitly requested
1270/// result features. Ordinary source bytes use the ordinary response budget.
1271pub const MAX_VECTOR_BYTES_PER_RESPONSE: usize = 64 * 1_024;
1272
1273/// Logical dense element representation. Physical ANN strategy remains a
1274/// reconstructible implementation choice and is not encoded here.
1275#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
1276#[serde(rename_all = "snake_case")]
1277pub enum DenseVectorElementType {
1278    F32,
1279    F16,
1280    I8,
1281}
1282
1283/// Logical sparse weight representation. Dimension identifiers remain u32
1284/// and are never hashed or dictionary-resolved by the engine.
1285#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
1286#[serde(rename_all = "snake_case")]
1287pub enum SparseVectorWeightType {
1288    F32,
1289    F16,
1290}
1291
1292impl SparseVectorWeightType {
1293    #[must_use]
1294    pub const fn width_bytes(self) -> usize {
1295        match self {
1296            Self::F32 => 4,
1297            Self::F16 => 2,
1298        }
1299    }
1300}
1301
1302/// One caller-supplied sparse coordinate. Canonical values are strictly
1303/// increasing by dimension and have a finite, non-zero weight.
1304#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
1305#[serde(deny_unknown_fields)]
1306pub struct SparseVectorEntry {
1307    pub dimension: u32,
1308    #[serde(deserialize_with = "deserialize_sparse_weight")]
1309    pub weight: f64,
1310}
1311
1312fn deserialize_sparse_weight<'de, D>(deserializer: D) -> Result<f64, D::Error>
1313where
1314    D: Deserializer<'de>,
1315{
1316    serde_json::Number::deserialize(deserializer)?
1317        .to_string()
1318        .parse::<f64>()
1319        .map_err(de::Error::custom)
1320}
1321
1322/// Native sparse value. Object form keeps this distinct from dense arrays and
1323/// leaves no string-feature hashing or dictionary semantics to infer.
1324#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1325#[serde(deny_unknown_fields)]
1326pub struct SparseVectorValue {
1327    #[serde(deserialize_with = "deserialize_bounded_sparse_entries")]
1328    pub entries: Vec<SparseVectorEntry>,
1329}
1330
1331/// Exact mapping semantics required to encode one sparse value.
1332#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1333pub struct SparseVectorDefinition {
1334    pub maximum_dimension: u32,
1335    pub weight_type: SparseVectorWeightType,
1336}
1337
1338/// One validated sparse coordinate in the mapping-selected logical precision.
1339#[derive(Clone, Copy, Debug, PartialEq)]
1340pub struct DecodedSparseVectorEntry {
1341    pub dimension: u32,
1342    pub weight: f32,
1343}
1344
1345impl SparseVectorDefinition {
1346    pub fn validate(self) -> Result<(), SparseVectorValueError> {
1347        if self.maximum_dimension > MAX_SPARSE_VECTOR_DIMENSION {
1348            return Err(SparseVectorValueError::InvalidMaximumDimension(self.maximum_dimension));
1349        }
1350        let maximum_bytes = MAX_SPARSE_VECTOR_NONZERO
1351            .checked_mul(4_usize.saturating_add(self.weight_type.width_bytes()))
1352            .ok_or(SparseVectorValueError::ByteLengthOverflow)?;
1353        if maximum_bytes > MAX_SPARSE_VECTOR_BYTES {
1354            return Err(SparseVectorValueError::FieldBytesExceeded(maximum_bytes));
1355        }
1356        Ok(())
1357    }
1358}
1359
1360impl SparseVectorValue {
1361    pub fn decode(
1362        &self,
1363        definition: SparseVectorDefinition,
1364    ) -> Result<Vec<DecodedSparseVectorEntry>, SparseVectorValueError> {
1365        definition.validate()?;
1366        self.validate_bounded(definition, MAX_SPARSE_VECTOR_BYTES)?;
1367        self.entries
1368            .iter()
1369            .enumerate()
1370            .map(|(ordinal, entry)| {
1371                let weight = entry.weight as f32;
1372                if !weight.is_finite() {
1373                    return Err(SparseVectorValueError::NonFiniteWeight(ordinal));
1374                }
1375                let weight = match definition.weight_type {
1376                    SparseVectorWeightType::F32 => canonical_vector_zero(weight),
1377                    SparseVectorWeightType::F16 => {
1378                        let bits = f32_to_f16_rne(weight).ok_or(SparseVectorValueError::F16Overflow(ordinal))?;
1379                        canonical_vector_zero(f16_to_f32(bits))
1380                    }
1381                };
1382                if weight == 0.0 {
1383                    return Err(SparseVectorValueError::ZeroWeight(ordinal));
1384                }
1385                Ok(DecodedSparseVectorEntry {
1386                    dimension: entry.dimension,
1387                    weight,
1388                })
1389            })
1390            .collect()
1391    }
1392
1393    pub fn validate_bounded(
1394        &self,
1395        definition: SparseVectorDefinition,
1396        maximum_bytes: usize,
1397    ) -> Result<usize, SparseVectorValueError> {
1398        definition.validate()?;
1399        if self.entries.is_empty() {
1400            return Err(SparseVectorValueError::Empty);
1401        }
1402        if self.entries.len() > MAX_SPARSE_VECTOR_NONZERO {
1403            return Err(SparseVectorValueError::TooManyEntries(self.entries.len()));
1404        }
1405        let bytes = self
1406            .entries
1407            .len()
1408            .checked_mul(4_usize.saturating_add(definition.weight_type.width_bytes()))
1409            .ok_or(SparseVectorValueError::ByteLengthOverflow)?;
1410        if bytes > maximum_bytes.min(MAX_SPARSE_VECTOR_BYTES) {
1411            return Err(SparseVectorValueError::FieldBytesExceeded(bytes));
1412        }
1413        let mut previous = None;
1414        for (ordinal, entry) in self.entries.iter().enumerate() {
1415            if entry.dimension > definition.maximum_dimension {
1416                return Err(SparseVectorValueError::DimensionOutOfRange {
1417                    ordinal,
1418                    dimension: entry.dimension,
1419                    maximum: definition.maximum_dimension,
1420                });
1421            }
1422            if previous.is_some_and(|dimension| entry.dimension <= dimension) {
1423                return Err(SparseVectorValueError::DimensionsNotStrict(ordinal));
1424            }
1425            if !entry.weight.is_finite() {
1426                return Err(SparseVectorValueError::NonFiniteWeight(ordinal));
1427            }
1428            if entry.weight == 0.0 {
1429                return Err(SparseVectorValueError::ZeroWeight(ordinal));
1430            }
1431            previous = Some(entry.dimension);
1432        }
1433        Ok(bytes)
1434    }
1435
1436    pub fn canonical_native_bytes(
1437        &self,
1438        definition: SparseVectorDefinition,
1439    ) -> Result<Vec<u8>, SparseVectorValueError> {
1440        let decoded = self.decode(definition)?;
1441        let mut bytes = Vec::with_capacity(
1442            decoded
1443                .len()
1444                .saturating_mul(4_usize.saturating_add(definition.weight_type.width_bytes())),
1445        );
1446        for entry in decoded {
1447            bytes.extend_from_slice(&entry.dimension.to_le_bytes());
1448            match definition.weight_type {
1449                SparseVectorWeightType::F32 => bytes.extend_from_slice(&entry.weight.to_bits().to_le_bytes()),
1450                SparseVectorWeightType::F16 => {
1451                    let bits = f32_to_f16_rne(entry.weight).ok_or(SparseVectorValueError::F16Overflow(0))?;
1452                    bytes.extend_from_slice(&bits.to_le_bytes());
1453                }
1454            }
1455        }
1456        Ok(bytes)
1457    }
1458}
1459
1460/// Decodes canonical interleaved `(u32 dimension, weight)` bytes. Immutable
1461/// bundle readers use this to reject malformed postings without inventing a
1462/// second sparse codec.
1463pub fn decode_sparse_native_bytes(
1464    definition: SparseVectorDefinition,
1465    bytes: &[u8],
1466) -> Result<Vec<DecodedSparseVectorEntry>, SparseVectorValueError> {
1467    definition.validate()?;
1468    let entry_bytes = 4_usize.saturating_add(definition.weight_type.width_bytes());
1469    if bytes.is_empty() || !bytes.len().is_multiple_of(entry_bytes) {
1470        return Err(SparseVectorValueError::FieldBytesExceeded(bytes.len()));
1471    }
1472    let count = bytes.len() / entry_bytes;
1473    if count > MAX_SPARSE_VECTOR_NONZERO || bytes.len() > MAX_SPARSE_VECTOR_BYTES {
1474        return Err(SparseVectorValueError::FieldBytesExceeded(bytes.len()));
1475    }
1476    let mut entries = Vec::with_capacity(count);
1477    let mut previous = None;
1478    for (ordinal, chunk) in bytes.chunks_exact(entry_bytes).enumerate() {
1479        let dimension = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
1480        if dimension > definition.maximum_dimension {
1481            return Err(SparseVectorValueError::DimensionOutOfRange {
1482                ordinal,
1483                dimension,
1484                maximum: definition.maximum_dimension,
1485            });
1486        }
1487        if previous.is_some_and(|value| dimension <= value) {
1488            return Err(SparseVectorValueError::DimensionsNotStrict(ordinal));
1489        }
1490        let weight = match definition.weight_type {
1491            SparseVectorWeightType::F32 => f32::from_le_bytes([chunk[4], chunk[5], chunk[6], chunk[7]]),
1492            SparseVectorWeightType::F16 => f16_to_f32(u16::from_le_bytes([chunk[4], chunk[5]])),
1493        };
1494        if !weight.is_finite() {
1495            return Err(SparseVectorValueError::NonFiniteWeight(ordinal));
1496        }
1497        let weight = canonical_vector_zero(weight);
1498        if weight == 0.0 {
1499            return Err(SparseVectorValueError::ZeroWeight(ordinal));
1500        }
1501        entries.push(DecodedSparseVectorEntry { dimension, weight });
1502        previous = Some(dimension);
1503    }
1504    Ok(entries)
1505}
1506
1507#[derive(Clone, Debug, Eq, PartialEq)]
1508pub enum SparseVectorValueError {
1509    Empty,
1510    InvalidMaximumDimension(u32),
1511    TooManyEntries(usize),
1512    ByteLengthOverflow,
1513    FieldBytesExceeded(usize),
1514    DimensionsNotStrict(usize),
1515    DimensionOutOfRange {
1516        ordinal: usize,
1517        dimension: u32,
1518        maximum: u32,
1519    },
1520    NonFiniteWeight(usize),
1521    ZeroWeight(usize),
1522    F16Overflow(usize),
1523}
1524
1525impl Display for SparseVectorValueError {
1526    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
1527        match self {
1528            Self::Empty => formatter.write_str("sparse vector value is empty"),
1529            Self::InvalidMaximumDimension(value) => {
1530                write!(formatter, "sparse maximum dimension {value} is invalid")
1531            }
1532            Self::TooManyEntries(value) => write!(formatter, "sparse vector has {value} non-zero entries"),
1533            Self::ByteLengthOverflow => formatter.write_str("sparse vector byte length overflowed"),
1534            Self::FieldBytesExceeded(value) => {
1535                write!(formatter, "sparse vector uses {value} bytes above its field ceiling")
1536            }
1537            Self::DimensionsNotStrict(ordinal) => {
1538                write!(formatter, "sparse vector dimension {ordinal} is duplicate or unsorted")
1539            }
1540            Self::DimensionOutOfRange {
1541                ordinal,
1542                dimension,
1543                maximum,
1544            } => write!(
1545                formatter,
1546                "sparse vector dimension {ordinal} value {dimension} exceeds {maximum}"
1547            ),
1548            Self::NonFiniteWeight(ordinal) => write!(formatter, "sparse vector weight {ordinal} is non-finite"),
1549            Self::ZeroWeight(ordinal) => write!(formatter, "sparse vector weight {ordinal} is zero"),
1550            Self::F16Overflow(ordinal) => write!(formatter, "sparse f16 weight {ordinal} overflows binary16"),
1551        }
1552    }
1553}
1554
1555impl Error for SparseVectorValueError {}
1556
1557fn deserialize_bounded_sparse_entries<'de, D>(deserializer: D) -> Result<Vec<SparseVectorEntry>, D::Error>
1558where
1559    D: Deserializer<'de>,
1560{
1561    struct SparseEntriesVisitor;
1562
1563    impl<'de> serde::de::Visitor<'de> for SparseEntriesVisitor {
1564        type Value = Vec<SparseVectorEntry>;
1565
1566        fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
1567            formatter.write_str("a bounded sparse entry sequence")
1568        }
1569
1570        fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
1571        where
1572            A: serde::de::SeqAccess<'de>,
1573        {
1574            if sequence
1575                .size_hint()
1576                .is_some_and(|length| length > MAX_SPARSE_VECTOR_NONZERO)
1577            {
1578                return Err(de::Error::custom("sparse vector exceeds the non-zero ceiling"));
1579            }
1580            let mut entries =
1581                Vec::with_capacity(sequence.size_hint().unwrap_or_default().min(MAX_SPARSE_VECTOR_NONZERO));
1582            while let Some(entry) = sequence.next_element::<SparseVectorEntry>()? {
1583                if entries.len() == MAX_SPARSE_VECTOR_NONZERO {
1584                    return Err(de::Error::custom("sparse vector exceeds the non-zero ceiling"));
1585                }
1586                entries.push(entry);
1587            }
1588            Ok(entries)
1589        }
1590    }
1591
1592    deserializer.deserialize_seq(SparseEntriesVisitor)
1593}
1594
1595impl DenseVectorElementType {
1596    #[must_use]
1597    pub const fn width_bytes(self) -> usize {
1598        match self {
1599            Self::F32 => 4,
1600            Self::F16 => 2,
1601            Self::I8 => 1,
1602        }
1603    }
1604}
1605
1606/// Positive finite scale for an `i8` logical dense field. The canonical wire
1607/// form is the shortest Rust decimal spelling of the exact f32 value.
1608#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
1609pub struct VectorQuantizationScale(u32);
1610
1611impl VectorQuantizationScale {
1612    pub fn new(value: f32) -> Result<Self, DenseVectorValueError> {
1613        if !value.is_finite() || value <= 0.0 {
1614            return Err(DenseVectorValueError::InvalidQuantizationScale);
1615        }
1616        Ok(Self(value.to_bits()))
1617    }
1618
1619    #[must_use]
1620    pub const fn get(self) -> f32 {
1621        f32::from_bits(self.0)
1622    }
1623}
1624
1625impl Serialize for VectorQuantizationScale {
1626    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1627    where
1628        S: Serializer,
1629    {
1630        serializer.serialize_str(&self.get().to_string())
1631    }
1632}
1633
1634impl<'de> Deserialize<'de> for VectorQuantizationScale {
1635    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1636    where
1637        D: Deserializer<'de>,
1638    {
1639        let text = String::deserialize(deserializer)?;
1640        let value = text.parse::<f32>().map_err(de::Error::custom)?;
1641        if value.to_string() != text {
1642            return Err(de::Error::custom("vector quantization scale is not canonical"));
1643        }
1644        Self::new(value).map_err(de::Error::custom)
1645    }
1646}
1647
1648/// Only little-endian binary vector values are canonical.
1649#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1650#[serde(rename_all = "snake_case")]
1651pub enum DenseVectorByteOrder {
1652    LittleEndian,
1653}
1654
1655/// Binary half of the native dense `VectorValue` union.
1656#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1657#[serde(deny_unknown_fields)]
1658pub struct DenseVectorBinaryValue {
1659    pub element_type: DenseVectorElementType,
1660    pub byte_order: DenseVectorByteOrder,
1661    pub data_base64: String,
1662    #[serde(default, skip_serializing_if = "Option::is_none")]
1663    pub quantization_scale: Option<VectorQuantizationScale>,
1664}
1665
1666/// One native dense value. Document source also admits the numeric variant as
1667/// its compact bare-array spelling; exact `_source` bytes are never rewritten.
1668#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1669#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
1670pub enum DenseVectorValue {
1671    Numeric {
1672        #[serde(deserialize_with = "deserialize_bounded_dense_numbers")]
1673        values: Vec<f64>,
1674    },
1675    Binary {
1676        #[serde(flatten)]
1677        value: DenseVectorBinaryValue,
1678    },
1679}
1680
1681impl DenseVectorValue {
1682    pub fn decode(&self, definition: DenseVectorDefinition) -> Result<Vec<f32>, DenseVectorValueError> {
1683        definition.validate()?;
1684        match self {
1685            Self::Numeric { values } => decode_dense_numeric_value(definition, values),
1686            Self::Binary { value } => decode_dense_binary_value(definition, value),
1687        }
1688    }
1689
1690    /// Returns the canonical native element bytes without metric
1691    /// normalization. These bytes are reconstructible from exact source and
1692    /// let immutable accelerators preserve the mapping's declared element
1693    /// representation instead of narrowing normalized scoring coordinates.
1694    pub fn canonical_native_bytes(&self, definition: DenseVectorDefinition) -> Result<Vec<u8>, DenseVectorValueError> {
1695        definition.validate()?;
1696        match self {
1697            Self::Numeric { values } => encode_dense_numeric_native_bytes(definition, values),
1698            Self::Binary { value } => decode_dense_binary_native_bytes(definition, value),
1699        }
1700    }
1701
1702    #[must_use]
1703    pub fn element_count(&self) -> Option<usize> {
1704        match self {
1705            Self::Numeric { values } => Some(values.len()),
1706            Self::Binary { .. } => None,
1707        }
1708    }
1709
1710    pub fn validate_bounded(&self, maximum_bytes: usize) -> Result<usize, DenseVectorValueError> {
1711        match self {
1712            Self::Numeric { values } => {
1713                if values.is_empty() {
1714                    return Err(DenseVectorValueError::Empty);
1715                }
1716                let bytes = values
1717                    .len()
1718                    .checked_mul(DenseVectorElementType::F32.width_bytes())
1719                    .ok_or(DenseVectorValueError::ByteLengthOverflow)?;
1720                if values.len() > usize::from(MAX_VECTOR_DIMENSIONS) || bytes > maximum_bytes {
1721                    return Err(DenseVectorValueError::FieldBytesExceeded(bytes));
1722                }
1723                if let Some(ordinal) = values.iter().position(|value| !value.is_finite()) {
1724                    return Err(DenseVectorValueError::NonFiniteElement(ordinal));
1725                }
1726                Ok(bytes)
1727            }
1728            Self::Binary { value } => {
1729                match (value.element_type, value.quantization_scale) {
1730                    (DenseVectorElementType::I8, Some(_))
1731                    | (DenseVectorElementType::F32 | DenseVectorElementType::F16, None) => {}
1732                    (DenseVectorElementType::I8, None) => {
1733                        return Err(DenseVectorValueError::MissingQuantizationScale);
1734                    }
1735                    (DenseVectorElementType::F32 | DenseVectorElementType::F16, Some(_)) => {
1736                        return Err(DenseVectorValueError::UnexpectedQuantizationScale);
1737                    }
1738                }
1739                if value.data_base64.is_empty() || value.data_base64.len() > maximum_bytes.div_ceil(3) * 4 {
1740                    return Err(DenseVectorValueError::FieldBytesExceeded(value.data_base64.len()));
1741                }
1742                let bytes = BASE64_STANDARD
1743                    .decode(value.data_base64.as_bytes())
1744                    .map_err(|_| DenseVectorValueError::InvalidBase64)?;
1745                if bytes.is_empty()
1746                    || bytes.len() > maximum_bytes
1747                    || !bytes.len().is_multiple_of(value.element_type.width_bytes())
1748                    || bytes.len() / value.element_type.width_bytes() > usize::from(MAX_VECTOR_DIMENSIONS)
1749                {
1750                    return Err(DenseVectorValueError::FieldBytesExceeded(bytes.len()));
1751                }
1752                if BASE64_STANDARD.encode(&bytes) != value.data_base64 {
1753                    return Err(DenseVectorValueError::InvalidBase64);
1754                }
1755                Ok(bytes.len())
1756            }
1757        }
1758    }
1759}
1760
1761/// Exact mapping semantics required to decode one dense value.
1762#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1763pub struct DenseVectorDefinition {
1764    pub dimensions: u16,
1765    pub element_type: DenseVectorElementType,
1766    pub metric: VectorMetric,
1767    pub quantization_scale: Option<VectorQuantizationScale>,
1768}
1769
1770impl DenseVectorDefinition {
1771    pub fn validate(self) -> Result<(), DenseVectorValueError> {
1772        if self.dimensions == 0 || self.dimensions > MAX_VECTOR_DIMENSIONS {
1773            return Err(DenseVectorValueError::InvalidDimensions(self.dimensions));
1774        }
1775        let bytes = usize::from(self.dimensions)
1776            .checked_mul(self.element_type.width_bytes())
1777            .ok_or(DenseVectorValueError::ByteLengthOverflow)?;
1778        if bytes > MAX_VECTOR_BYTES_PER_FIELD {
1779            return Err(DenseVectorValueError::FieldBytesExceeded(bytes));
1780        }
1781        match (self.element_type, self.quantization_scale) {
1782            (DenseVectorElementType::I8, Some(_))
1783            | (DenseVectorElementType::F32 | DenseVectorElementType::F16, None) => Ok(()),
1784            (DenseVectorElementType::I8, None) => Err(DenseVectorValueError::MissingQuantizationScale),
1785            (DenseVectorElementType::F32 | DenseVectorElementType::F16, Some(_)) => {
1786                Err(DenseVectorValueError::UnexpectedQuantizationScale)
1787            }
1788        }
1789    }
1790
1791    #[must_use]
1792    pub const fn encoded_bytes(self) -> usize {
1793        self.dimensions as usize * self.element_type.width_bytes()
1794    }
1795}
1796
1797/// A vector-set value is an explicit object and can never be confused with one
1798/// dense numeric array. Each member uses the ordinary dense value codec.
1799#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1800#[serde(deny_unknown_fields)]
1801pub struct VectorSetValue {
1802    #[serde(deserialize_with = "deserialize_bounded_vector_set")]
1803    pub vectors: Vec<DenseVectorValue>,
1804}
1805
1806/// Mapping-selected semantics for one bounded document vector set.
1807#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1808pub struct VectorSetDefinition {
1809    pub dense: DenseVectorDefinition,
1810    pub maximum_vectors: u16,
1811}
1812
1813/// One decoded vector set and its per-vector canonical element bytes.
1814#[derive(Clone, Debug, PartialEq)]
1815pub struct DecodedVectorSet {
1816    pub vectors: Vec<Vec<f32>>,
1817    pub native_vectors: Vec<Vec<u8>>,
1818    pub native_bytes: usize,
1819}
1820
1821impl VectorSetDefinition {
1822    pub fn validate(self) -> Result<(), VectorSetValueError> {
1823        self.dense.validate().map_err(VectorSetValueError::Dense)?;
1824        if self.maximum_vectors == 0 || usize::from(self.maximum_vectors) > MAX_VECTOR_SET_DOCUMENT_VECTORS {
1825            return Err(VectorSetValueError::InvalidMaximumVectors(self.maximum_vectors));
1826        }
1827        let maximum_bytes = usize::from(self.maximum_vectors)
1828            .checked_mul(self.dense.encoded_bytes())
1829            .ok_or(VectorSetValueError::ByteLengthOverflow)?;
1830        if maximum_bytes > MAX_VECTOR_SET_BYTES_PER_FIELD {
1831            return Err(VectorSetValueError::FieldBytesExceeded(maximum_bytes));
1832        }
1833        Ok(())
1834    }
1835}
1836
1837impl VectorSetValue {
1838    pub fn decode(
1839        &self,
1840        definition: VectorSetDefinition,
1841        maximum_vectors: usize,
1842        maximum_bytes: usize,
1843    ) -> Result<DecodedVectorSet, VectorSetValueError> {
1844        definition.validate()?;
1845        let admitted_vectors = maximum_vectors
1846            .min(usize::from(definition.maximum_vectors))
1847            .min(MAX_VECTOR_SET_DOCUMENT_VECTORS);
1848        if self.vectors.is_empty() {
1849            return Err(VectorSetValueError::Empty);
1850        }
1851        if self.vectors.len() > admitted_vectors {
1852            return Err(VectorSetValueError::TooManyVectors(self.vectors.len()));
1853        }
1854        let native_bytes = self
1855            .vectors
1856            .len()
1857            .checked_mul(definition.dense.encoded_bytes())
1858            .ok_or(VectorSetValueError::ByteLengthOverflow)?;
1859        if native_bytes > maximum_bytes.min(MAX_VECTOR_SET_BYTES_PER_FIELD) {
1860            return Err(VectorSetValueError::FieldBytesExceeded(native_bytes));
1861        }
1862        let mut vectors = Vec::with_capacity(self.vectors.len());
1863        let mut native_vectors = Vec::with_capacity(self.vectors.len());
1864        for value in &self.vectors {
1865            vectors.push(value.decode(definition.dense).map_err(VectorSetValueError::Dense)?);
1866            native_vectors.push(
1867                value
1868                    .canonical_native_bytes(definition.dense)
1869                    .map_err(VectorSetValueError::Dense)?,
1870            );
1871        }
1872        Ok(DecodedVectorSet {
1873            vectors,
1874            native_vectors,
1875            native_bytes,
1876        })
1877    }
1878}
1879
1880fn deserialize_bounded_vector_set<'de, D>(deserializer: D) -> Result<Vec<DenseVectorValue>, D::Error>
1881where
1882    D: Deserializer<'de>,
1883{
1884    struct VectorSetVisitor;
1885
1886    impl<'de> serde::de::Visitor<'de> for VectorSetVisitor {
1887        type Value = Vec<DenseVectorValue>;
1888
1889        fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
1890            formatter.write_str("a bounded sequence of dense vector values")
1891        }
1892
1893        fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
1894        where
1895            A: serde::de::SeqAccess<'de>,
1896        {
1897            if sequence
1898                .size_hint()
1899                .is_some_and(|length| length > MAX_VECTOR_SET_DOCUMENT_VECTORS)
1900            {
1901                return Err(de::Error::custom(
1902                    "vector set exceeds the document vector-count ceiling",
1903                ));
1904            }
1905            let mut vectors = Vec::with_capacity(
1906                sequence
1907                    .size_hint()
1908                    .unwrap_or_default()
1909                    .min(MAX_VECTOR_SET_DOCUMENT_VECTORS),
1910            );
1911            while let Some(vector) = sequence.next_element::<DenseVectorValue>()? {
1912                if vectors.len() == MAX_VECTOR_SET_DOCUMENT_VECTORS {
1913                    return Err(de::Error::custom(
1914                        "vector set exceeds the document vector-count ceiling",
1915                    ));
1916                }
1917                vectors.push(vector);
1918            }
1919            Ok(vectors)
1920        }
1921    }
1922
1923    deserializer.deserialize_seq(VectorSetVisitor)
1924}
1925
1926#[derive(Clone, Debug, Eq, PartialEq)]
1927pub enum VectorSetValueError {
1928    Empty,
1929    InvalidMaximumVectors(u16),
1930    TooManyVectors(usize),
1931    FieldBytesExceeded(usize),
1932    ByteLengthOverflow,
1933    Dense(DenseVectorValueError),
1934}
1935
1936impl Display for VectorSetValueError {
1937    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
1938        match self {
1939            Self::Empty => formatter.write_str("vector set cannot be empty"),
1940            Self::InvalidMaximumVectors(value) => {
1941                write!(formatter, "vector-set maximum vector count {value} is invalid")
1942            }
1943            Self::TooManyVectors(value) => write!(formatter, "vector set contains {value} vectors"),
1944            Self::FieldBytesExceeded(value) => write!(formatter, "vector set uses {value} element bytes"),
1945            Self::ByteLengthOverflow => formatter.write_str("vector-set byte length overflowed"),
1946            Self::Dense(error) => write!(formatter, "vector-set member is invalid: {error}"),
1947        }
1948    }
1949}
1950
1951impl Error for VectorSetValueError {}
1952
1953#[derive(Clone, Debug, Eq, PartialEq)]
1954pub enum DenseVectorValueError {
1955    Empty,
1956    InvalidDimensions(u16),
1957    DimensionMismatch { expected: u16, observed: usize },
1958    InvalidQuantizationScale,
1959    MissingQuantizationScale,
1960    UnexpectedQuantizationScale,
1961    QuantizationScaleMismatch,
1962    ElementTypeMismatch,
1963    ByteLengthOverflow,
1964    FieldBytesExceeded(usize),
1965    NonFiniteElement(usize),
1966    F16Overflow(usize),
1967    I8ElementOutOfRange(usize),
1968    InvalidBase64Length { expected: usize, observed: usize },
1969    InvalidBase64,
1970    DecodedByteLength { expected: usize, observed: usize },
1971    ZeroNorm,
1972}
1973
1974impl Display for DenseVectorValueError {
1975    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
1976        match self {
1977            Self::Empty => formatter.write_str("dense vector value is empty"),
1978            Self::InvalidDimensions(value) => write!(formatter, "dense vector dimensions {value} are invalid"),
1979            Self::DimensionMismatch { expected, observed } => {
1980                write!(formatter, "dense vector has {observed} elements; expected {expected}")
1981            }
1982            Self::InvalidQuantizationScale => {
1983                formatter.write_str("dense i8 quantization scale must be positive and finite")
1984            }
1985            Self::MissingQuantizationScale => {
1986                formatter.write_str("dense i8 value requires quantization scale metadata")
1987            }
1988            Self::UnexpectedQuantizationScale => {
1989                formatter.write_str("dense f32/f16 value cannot carry quantization metadata")
1990            }
1991            Self::QuantizationScaleMismatch => {
1992                formatter.write_str("dense i8 value quantization scale differs from its mapping")
1993            }
1994            Self::ElementTypeMismatch => {
1995                formatter.write_str("dense binary value element type differs from its mapping")
1996            }
1997            Self::ByteLengthOverflow => formatter.write_str("dense vector byte length overflowed"),
1998            Self::FieldBytesExceeded(value) => {
1999                write!(formatter, "dense vector uses {value} bytes above its field ceiling")
2000            }
2001            Self::NonFiniteElement(ordinal) => write!(formatter, "dense vector element {ordinal} is non-finite"),
2002            Self::F16Overflow(ordinal) => write!(formatter, "dense f16 element {ordinal} overflows binary16"),
2003            Self::I8ElementOutOfRange(ordinal) => {
2004                write!(
2005                    formatter,
2006                    "dense i8 element {ordinal} is not an exact integer in -128..=127"
2007                )
2008            }
2009            Self::InvalidBase64Length { expected, observed } => {
2010                write!(
2011                    formatter,
2012                    "dense binary base64 has {observed} bytes; expected {expected}"
2013                )
2014            }
2015            Self::InvalidBase64 => formatter.write_str("dense binary value is not canonical base64"),
2016            Self::DecodedByteLength { expected, observed } => {
2017                write!(
2018                    formatter,
2019                    "dense binary value has {observed} decoded bytes; expected {expected}"
2020                )
2021            }
2022            Self::ZeroNorm => formatter.write_str("dense cosine vector has zero norm"),
2023        }
2024    }
2025}
2026
2027impl Error for DenseVectorValueError {}
2028
2029fn deserialize_bounded_dense_numbers<'de, D>(deserializer: D) -> Result<Vec<f64>, D::Error>
2030where
2031    D: Deserializer<'de>,
2032{
2033    struct DenseNumbersVisitor;
2034
2035    impl<'de> Visitor<'de> for DenseNumbersVisitor {
2036        type Value = Vec<f64>;
2037
2038        fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
2039            write!(formatter, "at most {MAX_VECTOR_DIMENSIONS} dense vector numbers")
2040        }
2041
2042        fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
2043        where
2044            A: SeqAccess<'de>,
2045        {
2046            if sequence
2047                .size_hint()
2048                .is_some_and(|length| length > usize::from(MAX_VECTOR_DIMENSIONS))
2049            {
2050                return Err(de::Error::custom("dense vector exceeds the element ceiling"));
2051            }
2052            let mut values = Vec::with_capacity(
2053                sequence
2054                    .size_hint()
2055                    .unwrap_or_default()
2056                    .min(usize::from(MAX_VECTOR_DIMENSIONS)),
2057            );
2058            while let Some(value) = sequence.next_element::<f64>()? {
2059                if values.len() == usize::from(MAX_VECTOR_DIMENSIONS) {
2060                    return Err(de::Error::custom("dense vector exceeds the element ceiling"));
2061                }
2062                values.push(value);
2063            }
2064            Ok(values)
2065        }
2066    }
2067
2068    deserializer.deserialize_seq(DenseNumbersVisitor)
2069}
2070
2071pub fn decode_dense_numeric_value(
2072    definition: DenseVectorDefinition,
2073    values: &[f64],
2074) -> Result<Vec<f32>, DenseVectorValueError> {
2075    let bytes = encode_dense_numeric_native_bytes(definition, values)?;
2076    decode_dense_native_bytes(definition, &bytes)
2077}
2078
2079pub fn decode_dense_binary_value(
2080    definition: DenseVectorDefinition,
2081    value: &DenseVectorBinaryValue,
2082) -> Result<Vec<f32>, DenseVectorValueError> {
2083    let bytes = decode_dense_binary_native_bytes(definition, value)?;
2084    decode_dense_native_bytes(definition, &bytes)
2085}
2086
2087fn decode_dense_binary_native_bytes(
2088    definition: DenseVectorDefinition,
2089    value: &DenseVectorBinaryValue,
2090) -> Result<Vec<u8>, DenseVectorValueError> {
2091    definition.validate()?;
2092    if value.element_type != definition.element_type {
2093        return Err(DenseVectorValueError::ElementTypeMismatch);
2094    }
2095    if value.quantization_scale != definition.quantization_scale {
2096        return Err(DenseVectorValueError::QuantizationScaleMismatch);
2097    }
2098    let expected_bytes = definition.encoded_bytes();
2099    let expected_base64 = expected_bytes.div_ceil(3) * 4;
2100    if value.data_base64.len() != expected_base64 {
2101        return Err(DenseVectorValueError::InvalidBase64Length {
2102            expected: expected_base64,
2103            observed: value.data_base64.len(),
2104        });
2105    }
2106    let bytes = BASE64_STANDARD
2107        .decode(value.data_base64.as_bytes())
2108        .map_err(|_| DenseVectorValueError::InvalidBase64)?;
2109    if bytes.len() != expected_bytes {
2110        return Err(DenseVectorValueError::DecodedByteLength {
2111            expected: expected_bytes,
2112            observed: bytes.len(),
2113        });
2114    }
2115    if BASE64_STANDARD.encode(&bytes) != value.data_base64 {
2116        return Err(DenseVectorValueError::InvalidBase64);
2117    }
2118    Ok(bytes)
2119}
2120
2121fn encode_dense_numeric_native_bytes(
2122    definition: DenseVectorDefinition,
2123    values: &[f64],
2124) -> Result<Vec<u8>, DenseVectorValueError> {
2125    definition.validate()?;
2126    if values.len() != usize::from(definition.dimensions) {
2127        return Err(DenseVectorValueError::DimensionMismatch {
2128            expected: definition.dimensions,
2129            observed: values.len(),
2130        });
2131    }
2132    let mut bytes = Vec::with_capacity(definition.encoded_bytes());
2133    for (ordinal, value) in values.iter().copied().enumerate() {
2134        if !value.is_finite() {
2135            return Err(DenseVectorValueError::NonFiniteElement(ordinal));
2136        }
2137        match definition.element_type {
2138            DenseVectorElementType::F32 => {
2139                let value = canonical_vector_zero(value as f32);
2140                if !value.is_finite() {
2141                    return Err(DenseVectorValueError::NonFiniteElement(ordinal));
2142                }
2143                bytes.extend_from_slice(&value.to_bits().to_le_bytes());
2144            }
2145            DenseVectorElementType::F16 => {
2146                let value = value as f32;
2147                if !value.is_finite() {
2148                    return Err(DenseVectorValueError::F16Overflow(ordinal));
2149                }
2150                let bits = f32_to_f16_rne(value).ok_or(DenseVectorValueError::F16Overflow(ordinal))?;
2151                bytes.extend_from_slice(&bits.to_le_bytes());
2152            }
2153            DenseVectorElementType::I8 => {
2154                if value.fract() != 0.0 || !(-128.0..=127.0).contains(&value) {
2155                    return Err(DenseVectorValueError::I8ElementOutOfRange(ordinal));
2156                }
2157                bytes.push(value as i8 as u8);
2158            }
2159        }
2160    }
2161    Ok(bytes)
2162}
2163
2164/// Decodes one exact native element sequence and applies the mapping's metric
2165/// normalization. Durable bundle readers use this instead of widening and
2166/// then re-encoding a caller value.
2167pub fn decode_dense_native_bytes(
2168    definition: DenseVectorDefinition,
2169    bytes: &[u8],
2170) -> Result<Vec<f32>, DenseVectorValueError> {
2171    definition.validate()?;
2172    let expected = definition.encoded_bytes();
2173    if bytes.len() != expected {
2174        return Err(DenseVectorValueError::DecodedByteLength {
2175            expected,
2176            observed: bytes.len(),
2177        });
2178    }
2179    let mut decoded = Vec::with_capacity(usize::from(definition.dimensions));
2180    match definition.element_type {
2181        DenseVectorElementType::F32 => {
2182            for (ordinal, chunk) in bytes.as_chunks::<4>().0.iter().enumerate() {
2183                let value = f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
2184                if !value.is_finite() {
2185                    return Err(DenseVectorValueError::NonFiniteElement(ordinal));
2186                }
2187                decoded.push(canonical_vector_zero(value));
2188            }
2189        }
2190        DenseVectorElementType::F16 => {
2191            for (ordinal, chunk) in bytes.as_chunks::<2>().0.iter().enumerate() {
2192                let value = f16_to_f32(u16::from_le_bytes([chunk[0], chunk[1]]));
2193                if !value.is_finite() {
2194                    return Err(DenseVectorValueError::NonFiniteElement(ordinal));
2195                }
2196                decoded.push(canonical_vector_zero(value));
2197            }
2198        }
2199        DenseVectorElementType::I8 => {
2200            let scale = definition
2201                .quantization_scale
2202                .ok_or(DenseVectorValueError::MissingQuantizationScale)?
2203                .get();
2204            decoded.extend(
2205                bytes
2206                    .iter()
2207                    .map(|value| canonical_vector_zero((*value as i8 as f32) * scale)),
2208            );
2209        }
2210    }
2211    normalize_dense_for_metric(definition.metric, &mut decoded)?;
2212    Ok(decoded)
2213}
2214
2215fn normalize_dense_for_metric(metric: VectorMetric, values: &mut [f32]) -> Result<(), DenseVectorValueError> {
2216    if metric != VectorMetric::Cosine {
2217        return Ok(());
2218    }
2219    let squared_norm = values.iter().try_fold(0.0_f64, |sum, value| {
2220        let next = sum + f64::from(*value) * f64::from(*value);
2221        next.is_finite().then_some(next)
2222    });
2223    let norm = squared_norm
2224        .map(f64::sqrt)
2225        .filter(|value| *value > 0.0 && value.is_finite())
2226        .ok_or(DenseVectorValueError::ZeroNorm)?;
2227    for value in values {
2228        *value = canonical_vector_zero((f64::from(*value) / norm) as f32);
2229        if !value.is_finite() {
2230            return Err(DenseVectorValueError::NonFiniteElement(0));
2231        }
2232    }
2233    Ok(())
2234}
2235
2236const fn canonical_vector_zero(value: f32) -> f32 {
2237    if value == 0.0 { 0.0 } else { value }
2238}
2239
2240fn f32_to_f16_rne(value: f32) -> Option<u16> {
2241    if !value.is_finite() {
2242        return None;
2243    }
2244    let bits = canonical_vector_zero(value).to_bits();
2245    let sign = ((bits >> 16) & 0x8000) as u16;
2246    let exponent = ((bits >> 23) & 0xff) as i32 - 127;
2247    let fraction = bits & 0x7f_ffff;
2248    if exponent > 15 {
2249        return None;
2250    }
2251    if exponent >= -14 {
2252        let mut half_exponent = (exponent + 15) as u16;
2253        let mut half_fraction = (fraction >> 13) as u16;
2254        let remainder = fraction & 0x1fff;
2255        if remainder > 0x1000 || remainder == 0x1000 && half_fraction & 1 == 1 {
2256            half_fraction += 1;
2257            if half_fraction == 0x400 {
2258                half_fraction = 0;
2259                half_exponent += 1;
2260                if half_exponent >= 0x1f {
2261                    return None;
2262                }
2263            }
2264        }
2265        return Some(sign | (half_exponent << 10) | half_fraction);
2266    }
2267    let significand = u64::from((1_u32 << 23) | fraction);
2268    let shift = u32::try_from(-exponent - 1).ok()?;
2269    let rounded = round_shift_right_ties_even(significand, shift);
2270    if rounded >= 0x400 {
2271        Some(sign | 0x0400)
2272    } else {
2273        Some(sign | rounded as u16)
2274    }
2275}
2276
2277fn round_shift_right_ties_even(value: u64, shift: u32) -> u64 {
2278    if shift == 0 {
2279        return value;
2280    }
2281    if shift >= 64 {
2282        return 0;
2283    }
2284    let quotient = value >> shift;
2285    let remainder_mask = (1_u64 << shift) - 1;
2286    let remainder = value & remainder_mask;
2287    let halfway = 1_u64 << (shift - 1);
2288    quotient + u64::from(remainder > halfway || remainder == halfway && quotient & 1 == 1)
2289}
2290
2291fn f16_to_f32(bits: u16) -> f32 {
2292    let sign = u32::from(bits & 0x8000) << 16;
2293    let exponent = (bits >> 10) & 0x1f;
2294    let fraction = bits & 0x03ff;
2295    match (exponent, fraction) {
2296        (0, 0) => f32::from_bits(sign),
2297        (0, _) => {
2298            let value = f32::from(fraction) * 2.0_f32.powi(-24);
2299            if sign == 0 { value } else { -value }
2300        }
2301        (0x1f, _) => f32::from_bits(sign | 0x7f80_0000 | (u32::from(fraction) << 13)),
2302        _ => {
2303            let f32_exponent = u32::from(exponent) + 112;
2304            f32::from_bits(sign | (f32_exponent << 23) | (u32::from(fraction) << 13))
2305        }
2306    }
2307}
2308
2309/// Maximum UTF-8 bytes admitted by one mapped field path.
2310pub const MAX_MAPPING_FIELD_NAME_BYTES: usize = 1_024;
2311
2312/// Normalized mapping semantics for one schema generation.
2313///
2314/// Durable catalog records own the canonical bytes for this exact type.
2315/// Execution crates compile it but do not define a second mapping model.
2316#[derive(Clone, Debug, Eq, PartialEq)]
2317pub struct Mapping {
2318    pub format_version: u16,
2319    pub schema_generation: SchemaGeneration,
2320    pub mode: IndexMode,
2321    pub fields: BTreeMap<String, FieldMapping>,
2322    /// Semantics owned by the sole current mapping format.
2323    pub extensions: MappingExtensions,
2324}
2325
2326impl Mapping {
2327    #[must_use]
2328    pub fn empty(mode: IndexMode) -> Self {
2329        Self {
2330            format_version: MAPPING_FORMAT_VERSION,
2331            schema_generation: SchemaGeneration::new(1),
2332            mode,
2333            fields: BTreeMap::new(),
2334            extensions: crate::MappingExtensions::default(),
2335        }
2336    }
2337
2338    /// Completes format-4 metadata whose defaults are intrinsic to the native
2339    /// mapping model. Adapter-specific policy defaults are never applied here.
2340    #[must_use]
2341    pub fn normalized(mut self) -> Self {
2342        if self.format_version == MAPPING_FORMAT_VERSION {
2343            if self.extensions.analyzer_identities.is_empty() {
2344                self.extensions.analyzer_identities = self
2345                    .fields
2346                    .iter()
2347                    .filter(|(_, definition)| definition.field_type == FieldType::Text)
2348                    .map(|(field, _)| AnalyzerIdentity {
2349                        field: field.clone(),
2350                        index_analyzer: AnalyzerName::Standard,
2351                        search_analyzer: AnalyzerName::Standard,
2352                    })
2353                    .collect();
2354            }
2355            if self.extensions.keyword_limits.is_empty() {
2356                self.extensions.keyword_limits = self
2357                    .fields
2358                    .iter()
2359                    .filter(|(_, definition)| definition.field_type == FieldType::Keyword)
2360                    .map(|(field, _)| KeywordFieldPolicy {
2361                        field: field.clone(),
2362                        max_bytes: DEFAULT_KEYWORD_IGNORE_ABOVE,
2363                        normalizer: KeywordNormalizer::Keyword,
2364                    })
2365                    .collect();
2366            }
2367            if self.extensions.nested_paths.is_empty() {
2368                self.extensions.nested_paths = self
2369                    .fields
2370                    .iter()
2371                    .filter(|(_, definition)| definition.field_type == FieldType::Nested)
2372                    .map(|(field, _)| field.clone())
2373                    .collect();
2374            }
2375        }
2376        self
2377    }
2378
2379    /// Decodes exact canonical catalog/public bytes after a minimal,
2380    /// duplicate-safe current-format probe.
2381    pub fn decode_canonical(bytes: &[u8]) -> Result<Self, MappingCodecError> {
2382        if bytes.len() > MAX_MAPPING_BYTES {
2383            return Err(MappingCodecError::TooLarge(bytes.len()));
2384        }
2385        let mut probe = serde_json::Deserializer::from_slice(bytes);
2386        let version = MappingVersionProbe::deserialize(&mut probe).map_err(|error| {
2387            let message = error.to_string();
2388            if message.starts_with("duplicate mapping key '") {
2389                MappingCodecError::DuplicateKey(message)
2390            } else {
2391                MappingCodecError::Malformed(message)
2392            }
2393        })?;
2394        probe
2395            .end()
2396            .map_err(|error| MappingCodecError::Malformed(error.to_string()))?;
2397        if version != MAPPING_FORMAT_VERSION {
2398            return Err(MappingCodecError::UnsupportedVersion(version));
2399        }
2400        let mapping: Self =
2401            serde_json::from_slice(bytes).map_err(|error| MappingCodecError::Malformed(error.to_string()))?;
2402        mapping.validate().map_err(MappingCodecError::Invalid)?;
2403        let canonical =
2404            serde_json::to_vec(&mapping).map_err(|error| MappingCodecError::Malformed(error.to_string()))?;
2405        if canonical != bytes {
2406            return Err(MappingCodecError::NonCanonical);
2407        }
2408        Ok(mapping)
2409    }
2410
2411    pub fn encode_canonical(&self) -> Result<Vec<u8>, MappingCodecError> {
2412        self.validate().map_err(MappingCodecError::Invalid)?;
2413        serde_json::to_vec(self).map_err(|error| MappingCodecError::Malformed(error.to_string()))
2414    }
2415
2416    /// Validates the dependency-free semantic mapping contract.
2417    pub fn validate(&self) -> Result<(), MappingValidationError> {
2418        if self.format_version != MAPPING_FORMAT_VERSION {
2419            return Err(MappingValidationError::UnsupportedVersion(self.format_version));
2420        }
2421        let normalized = self.clone().normalized();
2422        let mapping = &normalized;
2423        if mapping.schema_generation.get() == 0 {
2424            return Err(MappingValidationError::ZeroSchemaGeneration);
2425        }
2426        if mapping.fields.len() > MAX_MAPPING_FIELDS {
2427            return Err(MappingValidationError::TooManyFields(mapping.fields.len()));
2428        }
2429        let mut vector_field_ids = std::collections::BTreeSet::new();
2430        let mut vector_fields = 0_usize;
2431        for (name, definition) in &mapping.fields {
2432            validate_mapping_field_name(name)?;
2433            for (separator, _) in name.match_indices('.') {
2434                let ancestor = &name[..separator];
2435                if mapping.fields.contains_key(ancestor)
2436                    && !mapping.extensions.nested_paths.iter().any(|path| path == ancestor)
2437                    && mapping
2438                        .extensions
2439                        .multi_field_projections
2440                        .iter()
2441                        .find(|projection| projection.child == *name)
2442                        .is_none_or(|projection| projection.parent != ancestor)
2443                {
2444                    return Err(MappingValidationError::ConflictingFieldPaths {
2445                        ancestor: ancestor.to_owned(),
2446                        descendant: name.clone(),
2447                    });
2448                }
2449            }
2450            match definition.field_type {
2451                FieldType::Vector {
2452                    format_version,
2453                    field_id,
2454                    dimensions,
2455                    metric,
2456                    element_type,
2457                    quantization_scale,
2458                } => {
2459                    vector_fields = vector_fields.saturating_add(1);
2460                    if format_version != VECTOR_MAPPING_FORMAT_VERSION {
2461                        return Err(MappingValidationError::UnsupportedVectorVersion(format_version));
2462                    }
2463                    if dimensions == 0 || dimensions > MAX_VECTOR_DIMENSIONS {
2464                        return Err(MappingValidationError::InvalidVectorDimensions {
2465                            field: name.clone(),
2466                            dimensions,
2467                        });
2468                    }
2469                    DenseVectorDefinition {
2470                        dimensions,
2471                        element_type,
2472                        metric,
2473                        quantization_scale,
2474                    }
2475                    .validate()
2476                    .map_err(|_| MappingValidationError::InvalidVectorElementContract(name.clone()))?;
2477                    if definition.stored {
2478                        return Err(MappingValidationError::StoredVectorField(name.clone()));
2479                    }
2480                    if !vector_field_ids.insert(field_id) {
2481                        return Err(MappingValidationError::DuplicateVectorFieldId(field_id));
2482                    }
2483                }
2484                FieldType::SparseVector {
2485                    format_version,
2486                    field_id,
2487                    maximum_dimension,
2488                    weight_type,
2489                } => {
2490                    vector_fields = vector_fields.saturating_add(1);
2491                    if format_version != SPARSE_VECTOR_MAPPING_FORMAT_VERSION {
2492                        return Err(MappingValidationError::UnsupportedSparseVectorVersion(format_version));
2493                    }
2494                    SparseVectorDefinition {
2495                        maximum_dimension,
2496                        weight_type,
2497                    }
2498                    .validate()
2499                    .map_err(|_| MappingValidationError::InvalidSparseVectorContract(name.clone()))?;
2500                    if definition.stored {
2501                        return Err(MappingValidationError::StoredVectorField(name.clone()));
2502                    }
2503                    if !vector_field_ids.insert(field_id) {
2504                        return Err(MappingValidationError::DuplicateVectorFieldId(field_id));
2505                    }
2506                }
2507                FieldType::VectorSet {
2508                    format_version,
2509                    field_id,
2510                    dimensions,
2511                    metric,
2512                    element_type,
2513                    quantization_scale,
2514                    maximum_vectors,
2515                } => {
2516                    vector_fields = vector_fields.saturating_add(1);
2517                    if format_version != VECTOR_SET_MAPPING_FORMAT_VERSION {
2518                        return Err(MappingValidationError::UnsupportedVectorSetVersion(format_version));
2519                    }
2520                    VectorSetDefinition {
2521                        dense: DenseVectorDefinition {
2522                            dimensions,
2523                            element_type,
2524                            metric,
2525                            quantization_scale,
2526                        },
2527                        maximum_vectors,
2528                    }
2529                    .validate()
2530                    .map_err(|_| MappingValidationError::InvalidVectorSetContract(name.clone()))?;
2531                    if definition.stored {
2532                        return Err(MappingValidationError::StoredVectorField(name.clone()));
2533                    }
2534                    if !vector_field_ids.insert(field_id) {
2535                        return Err(MappingValidationError::DuplicateVectorFieldId(field_id));
2536                    }
2537                }
2538                _ => {}
2539            }
2540            definition.validate(name)?;
2541        }
2542        if vector_fields > MAX_VECTOR_MAPPING_FIELDS {
2543            return Err(MappingValidationError::TooManyVectorFields(vector_fields));
2544        }
2545        mapping.extensions.validate(mapping)?;
2546        Ok(())
2547    }
2548
2549    /// Validates a mapping admitted for a new index or successor generation.
2550    pub fn validate_current(&self) -> Result<(), MappingValidationError> {
2551        self.validate()
2552    }
2553
2554    /// Returns whether `self` is an additive successor of `predecessor`.
2555    ///
2556    /// This is the shared semantic check used before entering the catalog
2557    /// CAS. Existing field definitions and every format-4 extension identity
2558    /// are immutable; a successor may only append new definitions and their
2559    /// matching metadata.
2560    #[must_use]
2561    pub fn is_additive_successor_of(&self, predecessor: &Self) -> bool {
2562        let predecessor = predecessor.clone().normalized();
2563        let successor = self.clone().normalized();
2564        predecessor.format_version == MAPPING_FORMAT_VERSION
2565            && successor.format_version == MAPPING_FORMAT_VERSION
2566            && predecessor.mode == successor.mode
2567            && predecessor.extensions.dynamic_policy == successor.extensions.dynamic_policy
2568            && predecessor
2569                .fields
2570                .iter()
2571                .all(|(path, field)| successor.fields.get(path) == Some(field))
2572            && predecessor
2573                .extensions
2574                .multi_field_projections
2575                .iter()
2576                .all(|entry| successor.extensions.multi_field_projections.contains(entry))
2577            && predecessor
2578                .extensions
2579                .analyzer_identities
2580                .iter()
2581                .all(|entry| successor.extensions.analyzer_identities.contains(entry))
2582            && predecessor
2583                .extensions
2584                .keyword_limits
2585                .iter()
2586                .all(|entry| successor.extensions.keyword_limits.contains(entry))
2587            && predecessor
2588                .extensions
2589                .nested_paths
2590                .iter()
2591                .all(|entry| successor.extensions.nested_paths.contains(entry))
2592            && successor
2593                .extensions
2594                .nested_paths
2595                .iter()
2596                .filter(|path| !predecessor.extensions.nested_paths.contains(path))
2597                .all(|path| {
2598                    predecessor.fields.keys().all(|field| {
2599                        field != path
2600                            && !field
2601                                .strip_prefix(path.as_str())
2602                                .is_some_and(|suffix| suffix.starts_with('.'))
2603                    })
2604                })
2605    }
2606}
2607
2608struct MappingVersionProbe;
2609
2610impl MappingVersionProbe {
2611    fn deserialize<'de, D>(deserializer: D) -> Result<u16, D::Error>
2612    where
2613        D: serde::Deserializer<'de>,
2614    {
2615        struct VersionVisitor;
2616
2617        impl<'de> Visitor<'de> for VersionVisitor {
2618            type Value = u16;
2619
2620            fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
2621                formatter.write_str("a mapping JSON object")
2622            }
2623
2624            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
2625            where
2626                A: MapAccess<'de>,
2627            {
2628                let mut keys = std::collections::BTreeSet::new();
2629                let mut version = None;
2630                while let Some(key) = map.next_key::<String>()? {
2631                    if !keys.insert(key.clone()) {
2632                        return Err(de::Error::custom(format!("duplicate mapping key '{key}'")));
2633                    }
2634                    let raw = map.next_value::<Box<serde_json::value::RawValue>>()?;
2635                    if key == "format_version" {
2636                        version = Some(
2637                            serde_json::from_str::<u16>(raw.get())
2638                                .map_err(|_| de::Error::custom("mapping format_version is invalid"))?,
2639                        );
2640                    }
2641                }
2642                version.ok_or_else(|| de::Error::custom("mapping format_version is missing"))
2643            }
2644        }
2645
2646        deserializer.deserialize_map(VersionVisitor)
2647    }
2648}
2649
2650impl Serialize for Mapping {
2651    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2652    where
2653        S: serde::Serializer,
2654    {
2655        use serde::ser::Error as _;
2656
2657        if self.format_version != MAPPING_FORMAT_VERSION {
2658            return Err(S::Error::custom(format!(
2659                "unsupported mapping format version {}",
2660                self.format_version
2661            )));
2662        }
2663        let normalized = self.clone().normalized();
2664        MappingWireRef {
2665            format_version: normalized.format_version,
2666            generation: normalized.schema_generation,
2667            mode: normalized.mode,
2668            dynamic_policy: &normalized.extensions.dynamic_policy,
2669            fields: &normalized.fields,
2670            multi_field_projections: &normalized.extensions.multi_field_projections,
2671            analyzer_identities: &normalized.extensions.analyzer_identities,
2672            keyword_limits: &normalized.extensions.keyword_limits,
2673            nested_paths: &normalized.extensions.nested_paths,
2674        }
2675        .serialize(serializer)
2676    }
2677}
2678
2679impl<'de> Deserialize<'de> for Mapping {
2680    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2681    where
2682        D: serde::Deserializer<'de>,
2683    {
2684        use serde::de::Error as _;
2685
2686        let value = serde_json::Value::deserialize(deserializer)?;
2687        let version = value
2688            .get("format_version")
2689            .and_then(serde_json::Value::as_u64)
2690            .and_then(|value| u16::try_from(value).ok())
2691            .ok_or_else(|| D::Error::custom("mapping format_version is missing or invalid"))?;
2692        if version != MAPPING_FORMAT_VERSION {
2693            return Err(D::Error::custom(format!(
2694                "unsupported mapping format version {version}"
2695            )));
2696        }
2697        let wire: MappingWire = serde_json::from_value(value).map_err(D::Error::custom)?;
2698        Ok(Self {
2699            format_version: wire.format_version,
2700            schema_generation: wire.generation,
2701            mode: wire.mode,
2702            fields: wire.fields,
2703            extensions: MappingExtensions {
2704                dynamic_policy: wire.dynamic_policy,
2705                multi_field_projections: wire.multi_field_projections,
2706                analyzer_identities: wire.analyzer_identities,
2707                keyword_limits: wire.keyword_limits,
2708                nested_paths: wire.nested_paths,
2709            },
2710        })
2711    }
2712}
2713
2714#[derive(Serialize)]
2715struct MappingWireRef<'a> {
2716    format_version: u16,
2717    generation: SchemaGeneration,
2718    mode: IndexMode,
2719    dynamic_policy: &'a DynamicMappingPolicy,
2720    fields: &'a BTreeMap<String, FieldMapping>,
2721    multi_field_projections: &'a Vec<MultiFieldProjection>,
2722    analyzer_identities: &'a Vec<AnalyzerIdentity>,
2723    keyword_limits: &'a Vec<KeywordFieldPolicy>,
2724    nested_paths: &'a Vec<String>,
2725}
2726
2727#[derive(Deserialize)]
2728#[serde(deny_unknown_fields)]
2729struct MappingWire {
2730    format_version: u16,
2731    generation: SchemaGeneration,
2732    mode: IndexMode,
2733    dynamic_policy: DynamicMappingPolicy,
2734    fields: BTreeMap<String, FieldMapping>,
2735    multi_field_projections: Vec<MultiFieldProjection>,
2736    analyzer_identities: Vec<AnalyzerIdentity>,
2737    keyword_limits: Vec<KeywordFieldPolicy>,
2738    nested_paths: Vec<String>,
2739}
2740
2741/// Current mapping semantics kept separate from the base field definitions.
2742#[derive(Clone, Debug, Default, Eq, PartialEq)]
2743pub struct MappingExtensions {
2744    pub dynamic_policy: DynamicMappingPolicy,
2745    pub multi_field_projections: Vec<MultiFieldProjection>,
2746    pub analyzer_identities: Vec<AnalyzerIdentity>,
2747    pub keyword_limits: Vec<KeywordFieldPolicy>,
2748    pub nested_paths: Vec<String>,
2749}
2750
2751impl MappingExtensions {
2752    fn validate(&self, mapping: &Mapping) -> Result<(), MappingValidationError> {
2753        if self.multi_field_projections.len() > MAX_MAPPING_FIELDS
2754            || self.analyzer_identities.len() > MAX_MAPPING_FIELDS
2755            || self.keyword_limits.len() > MAX_MAPPING_FIELDS
2756        {
2757            return Err(MappingValidationError::TooManyMappingMetadata);
2758        }
2759        if self.nested_paths.len() > MAX_NESTED_MAPPING_PATHS {
2760            return Err(MappingValidationError::TooManyNestedPaths(self.nested_paths.len()));
2761        }
2762        require_sorted_unique(
2763            self.multi_field_projections
2764                .iter()
2765                .map(|projection| projection.child.as_str()),
2766            "multi_field_projections",
2767        )?;
2768        require_sorted_unique(
2769            self.analyzer_identities.iter().map(|identity| identity.field.as_str()),
2770            "analyzer_identities",
2771        )?;
2772        require_sorted_unique(
2773            self.keyword_limits.iter().map(|policy| policy.field.as_str()),
2774            "keyword_limits",
2775        )?;
2776        require_sorted_unique(self.nested_paths.iter().map(String::as_str), "nested_paths")?;
2777        let nested_paths = self
2778            .nested_paths
2779            .iter()
2780            .map(String::as_str)
2781            .collect::<std::collections::BTreeSet<_>>();
2782        for child in &self.nested_paths {
2783            for (separator, _) in child.match_indices('.') {
2784                let parent = &child[..separator];
2785                if nested_paths.contains(parent) {
2786                    return Err(MappingValidationError::NestedPathOverlap {
2787                        parent: parent.to_owned(),
2788                        child: child.clone(),
2789                    });
2790                }
2791            }
2792        }
2793        for projection in &self.multi_field_projections {
2794            validate_mapping_field_name(&projection.child)?;
2795            validate_mapping_field_name(&projection.parent)?;
2796            let Some(child) = mapping.fields.get(&projection.child) else {
2797                return Err(MappingValidationError::MissingMultiField(projection.child.clone()));
2798            };
2799            let Some(parent) = mapping.fields.get(&projection.parent) else {
2800                return Err(MappingValidationError::MissingMultiFieldSource {
2801                    field: projection.child.clone(),
2802                    source: projection.parent.clone(),
2803                });
2804            };
2805            if !projection
2806                .child
2807                .strip_prefix(&projection.parent)
2808                .is_some_and(|suffix| suffix.starts_with('.') && suffix.len() > 1)
2809            {
2810                return Err(MappingValidationError::InvalidMultiFieldPath(projection.child.clone()));
2811            }
2812            if matches!(
2813                child.field_type,
2814                FieldType::Vector { .. }
2815                    | FieldType::SparseVector { .. }
2816                    | FieldType::VectorSet { .. }
2817                    | FieldType::Nested
2818            ) || matches!(
2819                parent.field_type,
2820                FieldType::Vector { .. }
2821                    | FieldType::SparseVector { .. }
2822                    | FieldType::VectorSet { .. }
2823                    | FieldType::Nested
2824            ) {
2825                return Err(MappingValidationError::InvalidMultiFieldEndpoint(
2826                    projection.child.clone(),
2827                ));
2828            }
2829            if !multi_field_types_are_compatible(parent.field_type, child.field_type) {
2830                return Err(MappingValidationError::InvalidMultiFieldEndpoint(
2831                    projection.child.clone(),
2832                ));
2833            }
2834        }
2835        for identity in &self.analyzer_identities {
2836            let Some(field) = mapping.fields.get(&identity.field) else {
2837                return Err(MappingValidationError::UnknownFieldMetadata(identity.field.clone()));
2838            };
2839            if field.field_type != FieldType::Text {
2840                return Err(MappingValidationError::AnalyzerOnNonTextField(identity.field.clone()));
2841            }
2842        }
2843        for policy in &self.keyword_limits {
2844            let Some(field) = mapping.fields.get(&policy.field) else {
2845                return Err(MappingValidationError::UnknownFieldMetadata(policy.field.clone()));
2846            };
2847            if field.field_type != FieldType::Keyword {
2848                return Err(MappingValidationError::KeywordPolicyOnNonKeywordField(
2849                    policy.field.clone(),
2850                ));
2851            }
2852            policy.validate()?;
2853            if let Some(MappingScalar::Keyword(value)) = &field.null_value
2854                && value.len() > policy.max_bytes as usize
2855            {
2856                return Err(MappingValidationError::InvalidKeywordNullValue(policy.field.clone()));
2857            }
2858        }
2859        for path in &self.nested_paths {
2860            if mapping
2861                .fields
2862                .get(path)
2863                .is_none_or(|field| field.field_type != FieldType::Nested)
2864            {
2865                return Err(MappingValidationError::InvalidNestedPath(path.clone()));
2866            }
2867            if path.split('.').count() > MAX_NESTED_MAPPING_DEPTH {
2868                return Err(MappingValidationError::NestedPathTooDeep(path.clone()));
2869            }
2870        }
2871        let text_fields = mapping
2872            .fields
2873            .iter()
2874            .filter_map(|(path, field)| (field.field_type == FieldType::Text).then_some(path.as_str()))
2875            .collect::<std::collections::BTreeSet<_>>();
2876        let analyzed_fields = self
2877            .analyzer_identities
2878            .iter()
2879            .map(|identity| identity.field.as_str())
2880            .collect::<std::collections::BTreeSet<_>>();
2881        if text_fields != analyzed_fields {
2882            return Err(MappingValidationError::IncompleteAnalyzerCoverage);
2883        }
2884        let keyword_fields = mapping
2885            .fields
2886            .iter()
2887            .filter_map(|(path, field)| (field.field_type == FieldType::Keyword).then_some(path.as_str()))
2888            .collect::<std::collections::BTreeSet<_>>();
2889        let limited_fields = self
2890            .keyword_limits
2891            .iter()
2892            .map(|policy| policy.field.as_str())
2893            .collect::<std::collections::BTreeSet<_>>();
2894        if keyword_fields != limited_fields {
2895            return Err(MappingValidationError::IncompleteKeywordCoverage);
2896        }
2897        let declared_nested = mapping
2898            .fields
2899            .iter()
2900            .filter_map(|(path, field)| (field.field_type == FieldType::Nested).then_some(path.as_str()))
2901            .collect::<std::collections::BTreeSet<_>>();
2902        let listed_nested = self
2903            .nested_paths
2904            .iter()
2905            .map(String::as_str)
2906            .collect::<std::collections::BTreeSet<_>>();
2907        if declared_nested != listed_nested {
2908            return Err(MappingValidationError::IncompleteNestedCoverage);
2909        }
2910        Ok(())
2911    }
2912}
2913
2914const fn multi_field_types_are_compatible(parent: FieldType, child: FieldType) -> bool {
2915    matches!(
2916        (parent, child),
2917        (
2918            FieldType::Text | FieldType::Keyword,
2919            FieldType::Text | FieldType::Keyword
2920        ) | (
2921            FieldType::U64 | FieldType::Date,
2922            FieldType::U64 | FieldType::Date | FieldType::F64
2923        ) | (FieldType::I64, FieldType::I64 | FieldType::F64)
2924            | (FieldType::F64, FieldType::F64)
2925            | (FieldType::Bool, FieldType::Bool)
2926    )
2927}
2928
2929fn require_sorted_unique<'a>(
2930    values: impl IntoIterator<Item = &'a str>,
2931    section: &'static str,
2932) -> Result<(), MappingValidationError> {
2933    let mut previous = None;
2934    for value in values {
2935        if previous.is_some_and(|prior| prior >= value) {
2936            return Err(MappingValidationError::MetadataNotSorted(section));
2937        }
2938        previous = Some(value);
2939    }
2940    Ok(())
2941}
2942
2943pub const MAX_DYNAMIC_FIELDS_PER_REQUEST: usize = 128;
2944pub const MAX_NESTED_MAPPING_DEPTH: usize = 8;
2945pub const MAX_NESTED_MAPPING_PATHS: usize = 64;
2946pub const DEFAULT_KEYWORD_IGNORE_ABOVE: u32 = 32_766;
2947pub const MAX_SCALAR_ARRAY_DEPTH: usize = 32;
2948pub const MAX_SCALAR_ARRAY_NODES: usize = 4_096;
2949pub const MAX_SCALAR_ARRAY_VALUES: usize = 2_048;
2950pub const MAX_MAPPING_BYTES: usize = 1_048_576;
2951pub const DYNAMIC_MAPPING_PROPOSAL_FORMAT_VERSION: u16 = 1;
2952
2953#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
2954#[serde(rename_all = "snake_case")]
2955pub enum DynamicMappingPolicy {
2956    True,
2957    #[default]
2958    False,
2959    Strict,
2960}
2961
2962/// Closed inferred type carried by the catalog-owned dynamic proposal.
2963#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2964#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
2965pub enum DynamicProposedTypeV1 {
2966    Boolean,
2967    I64,
2968    U64,
2969    F64,
2970    TextWithKeyword {
2971        index_analyzer: AnalyzerName,
2972        search_analyzer: AnalyzerName,
2973        keyword_child: String,
2974        keyword_normalizer: KeywordNormalizer,
2975        keyword_max_bytes: u32,
2976    },
2977}
2978
2979impl DynamicProposedTypeV1 {
2980    pub fn from_first_candidate(
2981        field_path: &str,
2982        candidate: &MappingScalar,
2983    ) -> Result<Self, DynamicMappingProposalError> {
2984        validate_mapping_field_name(field_path).map_err(DynamicMappingProposalError::InvalidField)?;
2985        Ok(match candidate {
2986            MappingScalar::Boolean(_) => Self::Boolean,
2987            MappingScalar::I64(_) => Self::I64,
2988            MappingScalar::U64(_) => Self::U64,
2989            MappingScalar::F64Bits(bits) => {
2990                let value = f64::from_bits(*bits);
2991                if !value.is_finite() {
2992                    return Err(DynamicMappingProposalError::InvalidCandidate);
2993                }
2994                Self::F64
2995            }
2996            MappingScalar::Keyword(_) => Self::TextWithKeyword {
2997                index_analyzer: AnalyzerName::Standard,
2998                search_analyzer: AnalyzerName::Standard,
2999                keyword_child: format!("{field_path}.keyword"),
3000                keyword_normalizer: KeywordNormalizer::Keyword,
3001                keyword_max_bytes: DEFAULT_KEYWORD_IGNORE_ABOVE,
3002            },
3003        })
3004    }
3005
3006    fn validate(&self, field_path: &str) -> Result<(), DynamicMappingProposalError> {
3007        if let Self::TextWithKeyword {
3008            keyword_child,
3009            keyword_max_bytes,
3010            ..
3011        } = self
3012            && (keyword_child != &format!("{field_path}.keyword")
3013                || *keyword_max_bytes == 0
3014                || *keyword_max_bytes > DEFAULT_KEYWORD_IGNORE_ABOVE)
3015        {
3016            return Err(DynamicMappingProposalError::InvalidProposedType);
3017        }
3018        Ok(())
3019    }
3020}
3021
3022#[derive(Serialize)]
3023struct DynamicMappingProposalDigestInput<'a> {
3024    format_version: u16,
3025    collection_index_ref: CollectionIndexRef,
3026    base_generation: SchemaGeneration,
3027    field_path: &'a str,
3028    first_non_null_candidate: &'a MappingScalar,
3029    proposed_type: &'a DynamicProposedTypeV1,
3030}
3031
3032/// Canonical proposal-v1 identity frozen by EC4. Attempt fencing is supplied
3033/// out of band by the coordinated command and is deliberately not serialized
3034/// here.
3035#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
3036#[serde(deny_unknown_fields)]
3037pub struct DynamicMappingProposalV1 {
3038    pub format_version: u16,
3039    pub collection_index_ref: CollectionIndexRef,
3040    pub base_generation: SchemaGeneration,
3041    pub field_path: String,
3042    pub first_non_null_candidate: MappingScalar,
3043    pub proposed_type: DynamicProposedTypeV1,
3044    pub proposal_digest: u64,
3045}
3046
3047impl DynamicMappingProposalV1 {
3048    pub fn new(
3049        collection_index_ref: CollectionIndexRef,
3050        base_generation: SchemaGeneration,
3051        field_path: String,
3052        mut first_non_null_candidate: MappingScalar,
3053    ) -> Result<Self, DynamicMappingProposalError> {
3054        if let MappingScalar::F64Bits(bits) = &mut first_non_null_candidate
3055            && f64::from_bits(*bits) == 0.0
3056        {
3057            *bits = 0.0_f64.to_bits();
3058        }
3059        let proposed_type = DynamicProposedTypeV1::from_first_candidate(&field_path, &first_non_null_candidate)?;
3060        let mut proposal = Self {
3061            format_version: DYNAMIC_MAPPING_PROPOSAL_FORMAT_VERSION,
3062            collection_index_ref,
3063            base_generation,
3064            field_path,
3065            first_non_null_candidate,
3066            proposed_type,
3067            proposal_digest: 0,
3068        };
3069        proposal.proposal_digest = proposal.exact_digest()?;
3070        proposal.validate()?;
3071        Ok(proposal)
3072    }
3073
3074    pub fn exact_digest(&self) -> Result<u64, DynamicMappingProposalError> {
3075        let input = DynamicMappingProposalDigestInput {
3076            format_version: self.format_version,
3077            collection_index_ref: self.collection_index_ref,
3078            base_generation: self.base_generation,
3079            field_path: &self.field_path,
3080            first_non_null_candidate: &self.first_non_null_candidate,
3081            proposed_type: &self.proposed_type,
3082        };
3083        let bytes =
3084            serde_json::to_vec(&input).map_err(|error| DynamicMappingProposalError::Codec(error.to_string()))?;
3085        if bytes.len() > MAX_MAPPING_BYTES {
3086            return Err(DynamicMappingProposalError::TooLarge(bytes.len()));
3087        }
3088        Ok(bytes.iter().fold(0xcbf2_9ce4_8422_2325_u64, |digest, byte| {
3089            (digest ^ u64::from(*byte)).wrapping_mul(0x0000_0100_0000_01b3)
3090        }))
3091    }
3092
3093    pub fn validate(&self) -> Result<(), DynamicMappingProposalError> {
3094        if self.format_version != DYNAMIC_MAPPING_PROPOSAL_FORMAT_VERSION {
3095            return Err(DynamicMappingProposalError::UnsupportedVersion(self.format_version));
3096        }
3097        validate_mapping_field_name(&self.field_path).map_err(DynamicMappingProposalError::InvalidField)?;
3098        if matches!(
3099            self.first_non_null_candidate,
3100            MappingScalar::F64Bits(bits) if f64::from_bits(bits) == 0.0 && bits != 0.0_f64.to_bits()
3101        ) {
3102            return Err(DynamicMappingProposalError::InvalidCandidate);
3103        }
3104        self.proposed_type.validate(&self.field_path)?;
3105        if DynamicProposedTypeV1::from_first_candidate(&self.field_path, &self.first_non_null_candidate)?
3106            != self.proposed_type
3107            || self.exact_digest()? != self.proposal_digest
3108        {
3109            return Err(DynamicMappingProposalError::IdentityMismatch);
3110        }
3111        Ok(())
3112    }
3113
3114    /// Verifies that `successor` is exactly the one-field (or text plus
3115    /// generated keyword child) additive delta described by this proposal.
3116    #[must_use]
3117    pub fn matches_successor(&self, predecessor: &Mapping, successor: &Mapping) -> bool {
3118        let Some(next_generation) = predecessor.schema_generation.checked_next() else {
3119            return false;
3120        };
3121        if self.validate().is_err()
3122            || self.base_generation != predecessor.schema_generation
3123            || successor.schema_generation != next_generation
3124        {
3125            return false;
3126        }
3127        let mut expected = predecessor.clone().normalized();
3128        expected.schema_generation = successor.schema_generation;
3129        let (field_type, text) = match &self.proposed_type {
3130            DynamicProposedTypeV1::Boolean => (FieldType::Bool, None),
3131            DynamicProposedTypeV1::I64 => (FieldType::I64, None),
3132            DynamicProposedTypeV1::U64 => (FieldType::U64, None),
3133            DynamicProposedTypeV1::F64 => (FieldType::F64, None),
3134            DynamicProposedTypeV1::TextWithKeyword {
3135                index_analyzer,
3136                search_analyzer,
3137                keyword_child,
3138                keyword_normalizer,
3139                keyword_max_bytes,
3140            } => (
3141                FieldType::Text,
3142                Some((
3143                    *index_analyzer,
3144                    *search_analyzer,
3145                    keyword_child,
3146                    *keyword_normalizer,
3147                    *keyword_max_bytes,
3148                )),
3149            ),
3150        };
3151        if expected
3152            .fields
3153            .insert(
3154                self.field_path.clone(),
3155                FieldMapping {
3156                    field_type,
3157                    stored: false,
3158                    null_value: None,
3159                },
3160            )
3161            .is_some()
3162        {
3163            return false;
3164        }
3165        if let Some((index_analyzer, search_analyzer, keyword_child, keyword_normalizer, keyword_max_bytes)) = text {
3166            if expected
3167                .fields
3168                .insert(
3169                    keyword_child.clone(),
3170                    FieldMapping {
3171                        field_type: FieldType::Keyword,
3172                        stored: false,
3173                        null_value: None,
3174                    },
3175                )
3176                .is_some()
3177            {
3178                return false;
3179            }
3180            expected.extensions.multi_field_projections.push(MultiFieldProjection {
3181                parent: self.field_path.clone(),
3182                child: keyword_child.clone(),
3183            });
3184            expected.extensions.analyzer_identities.push(AnalyzerIdentity {
3185                field: self.field_path.clone(),
3186                index_analyzer,
3187                search_analyzer,
3188            });
3189            expected.extensions.keyword_limits.push(KeywordFieldPolicy {
3190                field: keyword_child.clone(),
3191                max_bytes: keyword_max_bytes,
3192                normalizer: keyword_normalizer,
3193            });
3194            expected
3195                .extensions
3196                .multi_field_projections
3197                .sort_by(|left, right| left.child.cmp(&right.child));
3198            expected
3199                .extensions
3200                .analyzer_identities
3201                .sort_by(|left, right| left.field.cmp(&right.field));
3202            expected
3203                .extensions
3204                .keyword_limits
3205                .sort_by(|left, right| left.field.cmp(&right.field));
3206        }
3207        expected == successor.clone().normalized()
3208    }
3209
3210    pub fn encode_canonical(&self) -> Result<Vec<u8>, DynamicMappingProposalError> {
3211        self.validate()?;
3212        let bytes = serde_json::to_vec(self).map_err(|error| DynamicMappingProposalError::Codec(error.to_string()))?;
3213        if bytes.len() > MAX_MAPPING_BYTES {
3214            return Err(DynamicMappingProposalError::TooLarge(bytes.len()));
3215        }
3216        Ok(bytes)
3217    }
3218
3219    pub fn decode_canonical(bytes: &[u8]) -> Result<Self, DynamicMappingProposalError> {
3220        if bytes.len() > MAX_MAPPING_BYTES {
3221            return Err(DynamicMappingProposalError::TooLarge(bytes.len()));
3222        }
3223        let mut probe = serde_json::Deserializer::from_slice(bytes);
3224        let version = MappingVersionProbe::deserialize(&mut probe)
3225            .map_err(|error| DynamicMappingProposalError::Codec(error.to_string()))?;
3226        probe
3227            .end()
3228            .map_err(|error| DynamicMappingProposalError::Codec(error.to_string()))?;
3229        if version != DYNAMIC_MAPPING_PROPOSAL_FORMAT_VERSION {
3230            return Err(DynamicMappingProposalError::UnsupportedVersion(version));
3231        }
3232        let proposal: Self =
3233            serde_json::from_slice(bytes).map_err(|error| DynamicMappingProposalError::Codec(error.to_string()))?;
3234        proposal.validate()?;
3235        if proposal.encode_canonical()? != bytes {
3236            return Err(DynamicMappingProposalError::NonCanonical);
3237        }
3238        Ok(proposal)
3239    }
3240}
3241
3242#[derive(Debug)]
3243pub enum DynamicMappingProposalError {
3244    UnsupportedVersion(u16),
3245    InvalidField(MappingValidationError),
3246    InvalidCandidate,
3247    InvalidProposedType,
3248    IdentityMismatch,
3249    TooLarge(usize),
3250    NonCanonical,
3251    Codec(String),
3252}
3253
3254impl Display for DynamicMappingProposalError {
3255    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
3256        match self {
3257            Self::UnsupportedVersion(version) => {
3258                write!(formatter, "unsupported dynamic mapping proposal version {version}")
3259            }
3260            Self::InvalidField(error) => write!(formatter, "invalid dynamic mapping field: {error}"),
3261            Self::InvalidCandidate => formatter.write_str("invalid dynamic mapping first candidate"),
3262            Self::InvalidProposedType => formatter.write_str("invalid dynamic mapping proposed type"),
3263            Self::IdentityMismatch => formatter.write_str("dynamic mapping proposal identity mismatch"),
3264            Self::TooLarge(bytes) => write!(formatter, "dynamic mapping proposal is too large: {bytes} bytes"),
3265            Self::NonCanonical => formatter.write_str("dynamic mapping proposal is not canonical"),
3266            Self::Codec(error) => write!(formatter, "dynamic mapping proposal codec error: {error}"),
3267        }
3268    }
3269}
3270
3271impl Error for DynamicMappingProposalError {}
3272
3273#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
3274#[serde(deny_unknown_fields)]
3275pub struct MultiFieldProjection {
3276    pub parent: String,
3277    pub child: String,
3278}
3279
3280#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
3281#[serde(deny_unknown_fields)]
3282pub struct AnalyzerIdentity {
3283    pub field: String,
3284    pub index_analyzer: AnalyzerName,
3285    pub search_analyzer: AnalyzerName,
3286}
3287
3288#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
3289#[serde(rename_all = "snake_case")]
3290pub enum AnalyzerName {
3291    Keyword,
3292    Simple,
3293    Standard,
3294    Whitespace,
3295}
3296
3297#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
3298#[serde(deny_unknown_fields)]
3299pub struct KeywordFieldPolicy {
3300    pub field: String,
3301    pub max_bytes: u32,
3302    pub normalizer: KeywordNormalizer,
3303}
3304
3305impl KeywordFieldPolicy {
3306    fn validate(&self) -> Result<(), MappingValidationError> {
3307        if self.max_bytes == 0 || self.max_bytes > DEFAULT_KEYWORD_IGNORE_ABOVE {
3308            return Err(MappingValidationError::InvalidKeywordLimit {
3309                field: self.field.clone(),
3310                limit: self.max_bytes,
3311            });
3312        }
3313        Ok(())
3314    }
3315}
3316
3317#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
3318#[serde(rename_all = "snake_case")]
3319pub enum KeywordNormalizer {
3320    Keyword,
3321    Lowercase,
3322}
3323
3324/// Explicit normalized field definition.
3325#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
3326#[serde(deny_unknown_fields)]
3327pub struct FieldMapping {
3328    #[serde(rename = "type")]
3329    pub field_type: FieldType,
3330    #[serde(default)]
3331    pub stored: bool,
3332    #[serde(default, skip_serializing_if = "Option::is_none")]
3333    pub null_value: Option<MappingScalar>,
3334}
3335
3336impl FieldMapping {
3337    fn validate(&self, field: &str) -> Result<(), MappingValidationError> {
3338        let Some(value) = &self.null_value else {
3339            return Ok(());
3340        };
3341        let valid = match (self.field_type, value) {
3342            (FieldType::Text | FieldType::Keyword, MappingScalar::Keyword(_))
3343            | (FieldType::U64 | FieldType::Date, MappingScalar::U64(_))
3344            | (FieldType::I64, MappingScalar::I64(_))
3345            | (FieldType::Bool, MappingScalar::Boolean(_)) => true,
3346            (FieldType::U64 | FieldType::Date, MappingScalar::I64(value)) => *value >= 0,
3347            (FieldType::F64, MappingScalar::F64Bits(bits)) => f64::from_bits(*bits).is_finite(),
3348            (FieldType::F64, MappingScalar::I64(_) | MappingScalar::U64(_)) => true,
3349            _ => false,
3350        };
3351        if valid {
3352            Ok(())
3353        } else {
3354            Err(MappingValidationError::InvalidNullValue(field.to_owned()))
3355        }
3356    }
3357}
3358
3359#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
3360#[serde(tag = "kind", content = "value", rename_all = "snake_case", deny_unknown_fields)]
3361pub enum MappingScalar {
3362    Boolean(bool),
3363    I64(i64),
3364    U64(u64),
3365    F64Bits(u64),
3366    Keyword(String),
3367}
3368
3369/// Mapping types admitted by the native mapping model.
3370#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
3371#[serde(rename_all = "snake_case")]
3372pub enum FieldType {
3373    Text,
3374    Keyword,
3375    U64,
3376    /// Unsigned epoch milliseconds stored in the existing u64 fast column.
3377    Date,
3378    I64,
3379    F64,
3380    Bool,
3381    /// Object-correlation boundary reserved by format 4. EC6 owns physical
3382    /// nested rows and makes this field kind externally reachable.
3383    Nested,
3384    /// Immutable vector values live in a manifest-rooted segment sidecar, not
3385    /// in Tantivy. The catalog mapping remains their sole field authority.
3386    Vector {
3387        format_version: u16,
3388        field_id: VectorFieldId,
3389        dimensions: u16,
3390        metric: VectorMetric,
3391        element_type: DenseVectorElementType,
3392        #[serde(default, skip_serializing_if = "Option::is_none")]
3393        quantization_scale: Option<VectorQuantizationScale>,
3394    },
3395    /// Sparse numeric dimensions are explicit and immutable. No string
3396    /// feature hashing or dictionary authority exists inside the engine.
3397    SparseVector {
3398        format_version: u16,
3399        field_id: VectorFieldId,
3400        maximum_dimension: u32,
3401        weight_type: SparseVectorWeightType,
3402    },
3403    /// A bounded set of same-shaped dense vectors scored by the versioned
3404    /// late-interaction MaxSim contract. The exact source object is authority.
3405    VectorSet {
3406        format_version: u16,
3407        field_id: VectorFieldId,
3408        dimensions: u16,
3409        metric: VectorMetric,
3410        element_type: DenseVectorElementType,
3411        #[serde(default, skip_serializing_if = "Option::is_none")]
3412        quantization_scale: Option<VectorQuantizationScale>,
3413        maximum_vectors: u16,
3414    },
3415}
3416
3417/// Frozen vector distance semantics. Greater returned score is always better.
3418#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
3419#[serde(rename_all = "snake_case")]
3420pub enum VectorMetric {
3421    Cosine,
3422    DotProduct,
3423    SquaredL2,
3424}
3425
3426/// Returns whether a field path has one or more non-empty dot-delimited
3427/// components and contains no control bytes.
3428///
3429/// Dots are path separators, never literal key bytes. Callers that retain
3430/// arbitrary JSON therefore must reject object keys containing dots rather
3431/// than choosing a different interpretation at ingest and read time.
3432#[must_use]
3433pub fn is_canonical_field_path(path: &str) -> bool {
3434    !path.is_empty()
3435        && path.len() <= MAX_MAPPING_FIELD_NAME_BYTES
3436        && !path.bytes().any(|byte| byte <= 0x1f || byte == 0x7f)
3437        && path.split('.').all(|component| !component.is_empty())
3438}
3439
3440/// Resolves one canonical dotted path through a caller-provided object member
3441/// lookup. Invalid paths resolve to `None`.
3442///
3443/// Keeping component splitting here gives ingestion, native execution, and
3444/// compatibility response materialization one path interpretation without
3445/// making foundational types depend on a JSON implementation.
3446pub fn resolve_field_path<'a, T>(
3447    root: &'a T,
3448    path: &str,
3449    member: impl FnMut(&'a T, &str) -> Option<&'a T>,
3450) -> Option<&'a T> {
3451    if !is_canonical_field_path(path) {
3452        return None;
3453    }
3454    path.split('.').try_fold(root, member)
3455}
3456
3457/// Exact outcome of traversing a canonical field path.
3458#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3459pub enum FieldPathResolution<'a, T> {
3460    Missing,
3461    Found(&'a T),
3462    /// A present scalar/array was encountered where another object component
3463    /// was required.
3464    Blocked,
3465}
3466
3467/// Resolves a canonical path while distinguishing absence from a present
3468/// non-container intermediate.
3469pub fn resolve_field_path_checked<'a, T>(
3470    root: &'a T,
3471    path: &str,
3472    mut member: impl FnMut(&'a T, &str) -> Option<&'a T>,
3473    is_container: impl Fn(&T) -> bool,
3474) -> FieldPathResolution<'a, T> {
3475    if !is_canonical_field_path(path) {
3476        return FieldPathResolution::Missing;
3477    }
3478    let mut current = root;
3479    for component in path.split('.') {
3480        if !is_container(current) {
3481            return FieldPathResolution::Blocked;
3482        }
3483        let Some(next) = member(current, component) else {
3484            return FieldPathResolution::Missing;
3485        };
3486        current = next;
3487    }
3488    FieldPathResolution::Found(current)
3489}
3490
3491/// Validates one mapped field path without depending on an execution engine.
3492pub fn validate_mapping_field_name(name: &str) -> Result<(), MappingValidationError> {
3493    if !is_canonical_field_path(name) || name.starts_with('_') {
3494        return Err(MappingValidationError::InvalidFieldName(name.to_owned()));
3495    }
3496    Ok(())
3497}
3498
3499/// Invalid normalized mapping semantics.
3500#[derive(Clone, Debug, Eq, PartialEq)]
3501pub enum MappingValidationError {
3502    UnsupportedVersion(u16),
3503    ZeroSchemaGeneration,
3504    TooManyFields(usize),
3505    InvalidFieldName(String),
3506    ConflictingFieldPaths { ancestor: String, descendant: String },
3507    UnsupportedVectorVersion(u16),
3508    UnsupportedSparseVectorVersion(u16),
3509    UnsupportedVectorSetVersion(u16),
3510    TooManyVectorFields(usize),
3511    InvalidVectorDimensions { field: String, dimensions: u16 },
3512    InvalidVectorElementContract(String),
3513    InvalidSparseVectorContract(String),
3514    InvalidVectorSetContract(String),
3515    DuplicateVectorFieldId(VectorFieldId),
3516    StoredVectorField(String),
3517    MissingMultiField(String),
3518    MissingMultiFieldSource { field: String, source: String },
3519    InvalidMultiFieldPath(String),
3520    InvalidMultiFieldEndpoint(String),
3521    UnknownFieldMetadata(String),
3522    AnalyzerOnNonTextField(String),
3523    KeywordPolicyOnNonKeywordField(String),
3524    InvalidKeywordLimit { field: String, limit: u32 },
3525    InvalidKeywordNullValue(String),
3526    InvalidNullValue(String),
3527    InvalidNestedPath(String),
3528    NestedPathTooDeep(String),
3529    NestedPathOverlap { parent: String, child: String },
3530    IncompleteAnalyzerCoverage,
3531    IncompleteKeywordCoverage,
3532    IncompleteNestedCoverage,
3533    MetadataNotSorted(&'static str),
3534    TooManyMappingMetadata,
3535    TooManyNestedPaths(usize),
3536}
3537
3538impl Display for MappingValidationError {
3539    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
3540        match self {
3541            Self::UnsupportedVersion(version) => write!(formatter, "unsupported mapping format version {version}"),
3542            Self::ZeroSchemaGeneration => formatter.write_str("mapping schema generation must be nonzero"),
3543            Self::TooManyFields(count) => write!(formatter, "mapping has {count} fields"),
3544            Self::InvalidFieldName(name) => write!(formatter, "invalid mapped field name '{name}'"),
3545            Self::ConflictingFieldPaths { ancestor, descendant } => write!(
3546                formatter,
3547                "mapped scalar path '{ancestor}' conflicts with descendant '{descendant}'"
3548            ),
3549            Self::UnsupportedVectorVersion(version) => {
3550                write!(formatter, "unsupported vector mapping format version {version}")
3551            }
3552            Self::UnsupportedSparseVectorVersion(version) => {
3553                write!(formatter, "unsupported sparse-vector mapping format version {version}")
3554            }
3555            Self::UnsupportedVectorSetVersion(version) => {
3556                write!(formatter, "unsupported vector-set mapping format version {version}")
3557            }
3558            Self::TooManyVectorFields(count) => write!(formatter, "mapping has {count} vector fields"),
3559            Self::InvalidVectorDimensions { field, dimensions } => {
3560                write!(formatter, "vector field '{field}' has invalid dimensions {dimensions}")
3561            }
3562            Self::InvalidVectorElementContract(field) => {
3563                write!(
3564                    formatter,
3565                    "vector field '{field}' has invalid element or quantization semantics"
3566                )
3567            }
3568            Self::InvalidSparseVectorContract(field) => {
3569                write!(
3570                    formatter,
3571                    "sparse vector field '{field}' has invalid dimension or weight semantics"
3572                )
3573            }
3574            Self::InvalidVectorSetContract(field) => {
3575                write!(
3576                    formatter,
3577                    "vector-set field '{field}' has invalid element, dimension, or count semantics"
3578                )
3579            }
3580            Self::DuplicateVectorFieldId(field_id) => {
3581                write!(formatter, "vector field identity {field_id} is duplicated")
3582            }
3583            Self::StoredVectorField(field) => {
3584                write!(
3585                    formatter,
3586                    "vector field '{field}' cannot use Tantivy stored-field semantics"
3587                )
3588            }
3589            Self::MissingMultiField(field) => write!(formatter, "multi-field projection '{field}' has no field"),
3590            Self::MissingMultiFieldSource { field, source } => {
3591                write!(
3592                    formatter,
3593                    "multi-field projection '{field}' has missing source '{source}'"
3594                )
3595            }
3596            Self::InvalidMultiFieldPath(field) => {
3597                write!(
3598                    formatter,
3599                    "multi-field projection '{field}' is not a direct child of its source"
3600                )
3601            }
3602            Self::InvalidMultiFieldEndpoint(field) => {
3603                write!(
3604                    formatter,
3605                    "multi-field projection '{field}' has a nested or vector endpoint"
3606                )
3607            }
3608            Self::UnknownFieldMetadata(field) => {
3609                write!(formatter, "mapping metadata references unknown field '{field}'")
3610            }
3611            Self::AnalyzerOnNonTextField(field) => {
3612                write!(formatter, "analyzer identity is attached to non-text field '{field}'")
3613            }
3614            Self::KeywordPolicyOnNonKeywordField(field) => {
3615                write!(formatter, "keyword policy is attached to non-keyword field '{field}'")
3616            }
3617            Self::InvalidKeywordLimit { field, limit } => {
3618                write!(formatter, "keyword field '{field}' has invalid ignore_above {limit}")
3619            }
3620            Self::InvalidKeywordNullValue(field) => {
3621                write!(
3622                    formatter,
3623                    "keyword field '{field}' has a null_value beyond ignore_above"
3624                )
3625            }
3626            Self::InvalidNullValue(field) => write!(formatter, "field '{field}' has an invalid null_value"),
3627            Self::InvalidNestedPath(path) => write!(formatter, "nested path '{path}' has no nested field"),
3628            Self::NestedPathTooDeep(path) => write!(formatter, "nested path '{path}' exceeds maximum depth"),
3629            Self::NestedPathOverlap { parent, child } => {
3630                write!(formatter, "nested path '{child}' is below nested path '{parent}'")
3631            }
3632            Self::IncompleteAnalyzerCoverage => {
3633                formatter.write_str("every text field must have exactly one analyzer identity")
3634            }
3635            Self::IncompleteKeywordCoverage => {
3636                formatter.write_str("every keyword field must have exactly one keyword limit")
3637            }
3638            Self::IncompleteNestedCoverage => formatter.write_str("nested_paths must exactly cover nested fields"),
3639            Self::MetadataNotSorted(section) => {
3640                write!(formatter, "mapping section '{section}' is not sorted and unique")
3641            }
3642            Self::TooManyMappingMetadata => formatter.write_str("mapping metadata exceeds the field bound"),
3643            Self::TooManyNestedPaths(count) => write!(formatter, "mapping has {count} nested paths"),
3644        }
3645    }
3646}
3647
3648impl Error for MappingValidationError {}
3649
3650#[derive(Clone, Debug, Eq, PartialEq)]
3651pub enum MappingCodecError {
3652    TooLarge(usize),
3653    DuplicateKey(String),
3654    Malformed(String),
3655    UnsupportedVersion(u16),
3656    Invalid(MappingValidationError),
3657    NonCanonical,
3658}
3659
3660impl Display for MappingCodecError {
3661    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
3662        match self {
3663            Self::TooLarge(bytes) => write!(formatter, "mapping payload has {bytes} bytes"),
3664            Self::DuplicateKey(reason) | Self::Malformed(reason) => formatter.write_str(reason),
3665            Self::UnsupportedVersion(version) => write!(formatter, "unsupported mapping format version {version}"),
3666            Self::Invalid(error) => write!(formatter, "invalid mapping: {error}"),
3667            Self::NonCanonical => formatter.write_str("mapping payload is not canonical JSON"),
3668        }
3669    }
3670}
3671
3672impl Error for MappingCodecError {
3673    fn source(&self) -> Option<&(dyn Error + 'static)> {
3674        match self {
3675            Self::Invalid(error) => Some(error),
3676            _ => None,
3677        }
3678    }
3679}
3680
3681/// Object-storage residency assigned to one immutable segment file.
3682///
3683/// This enum reserves lifecycle policy identities. Only [`Self::Standard`] is
3684/// admitted until a provider-specific PUT plus metadata-readback probe passes.
3685#[non_exhaustive]
3686#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)]
3687#[serde(rename_all = "snake_case")]
3688pub enum StorageClass {
3689    #[default]
3690    Standard,
3691    Cold,
3692}
3693
3694/// Reconstructible local-residency preference for one index.
3695#[non_exhaustive]
3696#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)]
3697#[serde(rename_all = "snake_case")]
3698pub enum CacheClass {
3699    Pinned,
3700    #[default]
3701    Normal,
3702    ColdTolerant,
3703}
3704
3705impl CacheClass {
3706    /// Returns the stable catalog identity.
3707    #[must_use]
3708    pub const fn as_str(self) -> &'static str {
3709        match self {
3710            Self::Pinned => "pinned",
3711            Self::Normal => "normal",
3712            Self::ColdTolerant => "cold_tolerant",
3713        }
3714    }
3715}
3716
3717impl Display for CacheClass {
3718    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
3719        formatter.write_str(self.as_str())
3720    }
3721}
3722
3723impl StorageClass {
3724    /// Returns whether serde may omit this value from a versioned parent.
3725    #[must_use]
3726    pub const fn is_standard(&self) -> bool {
3727        matches!(self, Self::Standard)
3728    }
3729
3730    /// Returns the stable manifest identity.
3731    #[must_use]
3732    pub const fn as_str(self) -> &'static str {
3733        match self {
3734            Self::Standard => "standard",
3735            Self::Cold => "cold",
3736        }
3737    }
3738}
3739
3740impl Display for StorageClass {
3741    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
3742        formatter.write_str(self.as_str())
3743    }
3744}
3745
3746/// Reserved verb carried by a future versioned lifecycle-stage parent.
3747///
3748/// This enum does not execute lifecycle work. ADR-0016 reserves the wire names
3749/// before an index-config format depends on them.
3750#[non_exhaustive]
3751#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
3752#[serde(rename_all = "snake_case")]
3753pub enum LifecycleVerb {
3754    Transition,
3755    AggregateThenDelete,
3756}
3757
3758/// A validated document identifier.
3759#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
3760#[serde(transparent)]
3761pub struct DocumentId(String);
3762
3763impl DocumentId {
3764    /// Constructs a non-empty document identifier no larger than 512 bytes.
3765    pub fn new(value: impl Into<String>) -> Result<Self, IdentifierError> {
3766        let value = value.into();
3767        if value.is_empty() {
3768            return Err(IdentifierError::EmptyDocumentId);
3769        }
3770        if value.len() > 512 {
3771            return Err(IdentifierError::DocumentIdTooLong(value.len()));
3772        }
3773        Ok(Self(value))
3774    }
3775
3776    /// Returns the identifier text.
3777    #[must_use]
3778    pub fn as_str(&self) -> &str {
3779        &self.0
3780    }
3781
3782    /// Consumes this identifier.
3783    #[must_use]
3784    pub fn into_inner(self) -> String {
3785        self.0
3786    }
3787}
3788
3789impl<'de> Deserialize<'de> for DocumentId {
3790    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3791    where
3792        D: Deserializer<'de>,
3793    {
3794        let value = String::deserialize(deserializer)?;
3795        Self::new(value).map_err(serde::de::Error::custom)
3796    }
3797}
3798
3799impl Display for DocumentId {
3800    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
3801        formatter.write_str(self.as_str())
3802    }
3803}
3804
3805/// A bounded client token used to deduplicate one logical write.
3806#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
3807#[serde(transparent)]
3808pub struct IdempotencyKey(String);
3809
3810impl IdempotencyKey {
3811    /// Constructs a non-empty UTF-8 key no larger than 256 bytes.
3812    pub fn new(value: impl Into<String>) -> Result<Self, IdentifierError> {
3813        let value = value.into();
3814        if value.is_empty() {
3815            return Err(IdentifierError::EmptyIdempotencyKey);
3816        }
3817        if value.len() > 256 {
3818            return Err(IdentifierError::IdempotencyKeyTooLong(value.len()));
3819        }
3820        Ok(Self(value))
3821    }
3822
3823    #[must_use]
3824    pub fn as_str(&self) -> &str {
3825        &self.0
3826    }
3827}
3828
3829impl<'de> Deserialize<'de> for IdempotencyKey {
3830    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3831    where
3832        D: Deserializer<'de>,
3833    {
3834        let value = String::deserialize(deserializer)?;
3835        Self::new(value).map_err(serde::de::Error::custom)
3836    }
3837}
3838
3839impl Display for IdempotencyKey {
3840    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
3841        formatter.write_str(self.as_str())
3842    }
3843}
3844
3845/// A bounded opaque point-in-time lease identifier.
3846#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
3847#[serde(transparent)]
3848pub struct PitId(String);
3849
3850impl PitId {
3851    /// Constructs a non-empty opaque PIT identifier no larger than 512 bytes.
3852    pub fn new(value: impl Into<String>) -> Result<Self, IdentifierError> {
3853        let value = value.into();
3854        if value.is_empty() {
3855            return Err(IdentifierError::EmptyPitId);
3856        }
3857        if value.len() > 512 {
3858            return Err(IdentifierError::PitIdTooLong(value.len()));
3859        }
3860        Ok(Self(value))
3861    }
3862
3863    /// Returns the opaque identifier text.
3864    #[must_use]
3865    pub fn as_str(&self) -> &str {
3866        &self.0
3867    }
3868}
3869
3870impl<'de> Deserialize<'de> for PitId {
3871    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3872    where
3873        D: Deserializer<'de>,
3874    {
3875        let value = String::deserialize(deserializer)?;
3876        Self::new(value).map_err(serde::de::Error::custom)
3877    }
3878}
3879
3880impl Display for PitId {
3881    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
3882        formatter.write_str(self.as_str())
3883    }
3884}
3885
3886/// Durable format version for recovery-era-scoped PIT identifiers.
3887pub const SCOPED_PIT_ID_FORMAT_VERSION: u16 = 1;
3888
3889/// A PIT identifier whose namespace is fenced by the coordinated recovery era.
3890///
3891/// The era is part of the identifier rather than adjacent command metadata so
3892/// a nonce may be reused only after the durable authority has advanced.
3893#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize)]
3894#[serde(deny_unknown_fields)]
3895pub struct ScopedPitId {
3896    format_version: u16,
3897    recovery_era: RecoveryEra,
3898    nonce: PitId,
3899}
3900
3901impl ScopedPitId {
3902    #[must_use]
3903    pub const fn new(recovery_era: RecoveryEra, nonce: PitId) -> Self {
3904        Self {
3905            format_version: SCOPED_PIT_ID_FORMAT_VERSION,
3906            recovery_era,
3907            nonce,
3908        }
3909    }
3910
3911    #[must_use]
3912    pub const fn recovery_era(&self) -> RecoveryEra {
3913        self.recovery_era
3914    }
3915
3916    #[must_use]
3917    pub const fn nonce(&self) -> &PitId {
3918        &self.nonce
3919    }
3920}
3921
3922impl Ord for ScopedPitId {
3923    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
3924        self.recovery_era
3925            .to_be_bytes()
3926            .cmp(&other.recovery_era.to_be_bytes())
3927            .then_with(|| self.nonce.cmp(&other.nonce))
3928    }
3929}
3930
3931impl PartialOrd for ScopedPitId {
3932    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
3933        Some(self.cmp(other))
3934    }
3935}
3936
3937impl<'de> Deserialize<'de> for ScopedPitId {
3938    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3939    where
3940        D: Deserializer<'de>,
3941    {
3942        #[derive(Deserialize)]
3943        #[serde(deny_unknown_fields)]
3944        struct Wire {
3945            format_version: u16,
3946            recovery_era: RecoveryEra,
3947            nonce: PitId,
3948        }
3949
3950        let wire = Wire::deserialize(deserializer)?;
3951        if wire.format_version != SCOPED_PIT_ID_FORMAT_VERSION {
3952            return Err(serde::de::Error::custom(
3953                IdentifierError::UnsupportedScopedPitIdVersion(wire.format_version),
3954            ));
3955        }
3956        Ok(Self::new(wire.recovery_era, wire.nonce))
3957    }
3958}
3959
3960/// Stable BLAKE3 fingerprint of the complete client mutation intent.
3961#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
3962#[serde(transparent)]
3963pub struct OperationFingerprint([u8; 32]);
3964
3965impl OperationFingerprint {
3966    #[must_use]
3967    pub const fn new(bytes: [u8; 32]) -> Self {
3968        Self(bytes)
3969    }
3970
3971    #[must_use]
3972    pub fn from_payload(bytes: &[u8]) -> Self {
3973        Self(*blake3::hash(bytes).as_bytes())
3974    }
3975
3976    #[must_use]
3977    pub const fn as_bytes(&self) -> &[u8; 32] {
3978        &self.0
3979    }
3980}
3981
3982/// A validated relative immutable-object key.
3983#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
3984#[serde(transparent)]
3985pub struct ObjectKey(String);
3986
3987impl ObjectKey {
3988    /// Constructs a safe relative object key.
3989    pub fn new(value: impl Into<String>) -> Result<Self, IdentifierError> {
3990        let value = value.into();
3991        if value.is_empty() {
3992            return Err(IdentifierError::EmptyObjectKey);
3993        }
3994        if value.starts_with('/') || value.contains('\\') {
3995            return Err(IdentifierError::InvalidObjectKey(value));
3996        }
3997        if value
3998            .split('/')
3999            .any(|component| component.is_empty() || component == "." || component == "..")
4000        {
4001            return Err(IdentifierError::InvalidObjectKey(value));
4002        }
4003        Ok(Self(value))
4004    }
4005
4006    /// Returns the normalized relative key.
4007    #[must_use]
4008    pub fn as_str(&self) -> &str {
4009        &self.0
4010    }
4011
4012    /// Consumes this object key.
4013    ///
4014    /// The allocation can exceed the encoded key length. Memory admissions
4015    /// retaining this key must use `allocated_capacity_bytes` instead.
4016    #[must_use]
4017    pub fn into_inner(self) -> String {
4018        self.0
4019    }
4020
4021    /// Owned string allocation capacity, excluding the inline string header.
4022    #[must_use]
4023    pub fn allocated_capacity_bytes(&self) -> usize {
4024        self.0.capacity()
4025    }
4026}
4027
4028impl<'de> Deserialize<'de> for ObjectKey {
4029    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
4030    where
4031        D: Deserializer<'de>,
4032    {
4033        let value = String::deserialize(deserializer)?;
4034        Self::new(value).map_err(serde::de::Error::custom)
4035    }
4036}
4037
4038impl Display for ObjectKey {
4039    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
4040        formatter.write_str(self.as_str())
4041    }
4042}
4043
4044/// Current transitive root-set locator. The page count selects the current
4045/// single-page or linked-page physical encoding.
4046pub const ROOT_SET_REF_FORMAT_VERSION: u16 = 2;
4047pub const MAX_ROOT_SET_PAGES: u32 = 256;
4048pub const MAX_ROOT_SET_SCHEMA_GENERATIONS: usize = 256;
4049pub const MAX_ROOT_SET_OBJECT_KEY_BYTES: usize = 1_024;
4050pub const PINNED_MANIFEST_BINDING_FORMAT_VERSION: u16 = 1;
4051
4052/// Never-reused physical identity of one immutable object.
4053#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
4054#[serde(deny_unknown_fields)]
4055pub struct PhysicalObjectId {
4056    pub backend_authority: BackendAuthorityId,
4057    pub object_incarnation: ObjectIncarnation,
4058    pub object_key: ObjectKey,
4059}
4060
4061impl PhysicalObjectId {
4062    pub fn validate(&self) -> Result<(), RootSetRefError> {
4063        let length = self.object_key.as_str().len();
4064        if !(1..=MAX_ROOT_SET_OBJECT_KEY_BYTES).contains(&length) {
4065            return Err(RootSetRefError::ObjectKeyLength(length));
4066        }
4067        Ok(())
4068    }
4069}
4070
4071/// Integrity-qualified physical object used inside immutable root-set pages.
4072#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
4073#[serde(deny_unknown_fields)]
4074pub struct ObjectIdentity {
4075    pub physical: PhysicalObjectId,
4076    pub content_checksum: [u8; 32],
4077}
4078
4079impl ObjectIdentity {
4080    pub fn validate(&self) -> Result<(), RootSetRefError> {
4081        self.physical.validate()
4082    }
4083}
4084
4085/// Bounded immutable root-set header copied only from an authoritative source.
4086#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
4087#[serde(deny_unknown_fields)]
4088pub struct RootSetRef {
4089    pub format_version: u16,
4090    pub root: PhysicalObjectId,
4091    pub content_digest: [u8; 32],
4092    pub item_count: u64,
4093    pub page_count: u32,
4094    pub schema_generations: Vec<SchemaGeneration>,
4095}
4096
4097impl RootSetRef {
4098    pub fn validate(&self) -> Result<(), RootSetRefError> {
4099        if self.format_version != ROOT_SET_REF_FORMAT_VERSION {
4100            return Err(RootSetRefError::UnsupportedVersion(self.format_version));
4101        }
4102        self.root.validate()?;
4103        if self.item_count == 0
4104            || self.page_count == 0
4105            || self.page_count > MAX_ROOT_SET_PAGES
4106            || u64::from(self.page_count) > self.item_count
4107        {
4108            return Err(RootSetRefError::InvalidCounts {
4109                items: self.item_count,
4110                pages: self.page_count,
4111            });
4112        }
4113        if self.schema_generations.len() > MAX_ROOT_SET_SCHEMA_GENERATIONS
4114            || self.schema_generations.windows(2).any(|pair| pair[0] >= pair[1])
4115        {
4116            return Err(RootSetRefError::InvalidSchemaGenerations);
4117        }
4118        Ok(())
4119    }
4120}
4121
4122#[derive(Clone, Debug, Eq, PartialEq)]
4123pub enum RootSetRefError {
4124    UnsupportedVersion(u16),
4125    ObjectKeyLength(usize),
4126    InvalidCounts { items: u64, pages: u32 },
4127    InvalidSchemaGenerations,
4128}
4129
4130impl Display for RootSetRefError {
4131    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
4132        match self {
4133            Self::UnsupportedVersion(version) => write!(formatter, "unsupported root-set reference version {version}"),
4134            Self::ObjectKeyLength(length) => {
4135                write!(formatter, "root-set object key is {length} bytes; expected 1..=1024")
4136            }
4137            Self::InvalidCounts { items, pages } => {
4138                write!(formatter, "root-set counts are invalid: items={items}, pages={pages}")
4139            }
4140            Self::InvalidSchemaGenerations => {
4141                formatter.write_str("root-set schema generations are not bounded strict ascending values")
4142            }
4143        }
4144    }
4145}
4146
4147impl Error for RootSetRefError {}
4148
4149/// Exact immutable-manifest codec selected by a durable PIT binding.
4150#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
4151#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
4152pub enum PinnedManifestDecodeProvenanceV1 {
4153    ScopedManifestV3,
4154}
4155
4156/// Complete immutable manifest binding copied into a durable PIT lease.
4157///
4158/// This is read authority only. It cannot publish or replace a manifest, and
4159/// retaining it beside a PIT is the immutable snapshot ownership transfer
4160/// required when the mutable current pointer later advances.
4161#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
4162#[serde(deny_unknown_fields)]
4163pub struct PinnedManifestBindingV1 {
4164    pub format_version: u16,
4165    pub shard_key: ShardKey,
4166    pub admission_generation: AdmissionGeneration,
4167    pub routing_revision: RoutingRevision,
4168    pub data_backend_authority: BackendAuthorityId,
4169    pub generation: ManifestGeneration,
4170    pub fence: FenceToken,
4171    pub primary_term: PrimaryTerm,
4172    pub manifest: ObjectIdentity,
4173    pub root_set: RootSetRef,
4174    pub committed_through: SeqNo,
4175    pub decode_provenance: PinnedManifestDecodeProvenanceV1,
4176}
4177
4178impl PinnedManifestBindingV1 {
4179    pub fn validate(&self) -> Result<(), PinnedManifestBindingError> {
4180        if self.format_version != PINNED_MANIFEST_BINDING_FORMAT_VERSION {
4181            return Err(PinnedManifestBindingError::UnsupportedVersion(self.format_version));
4182        }
4183        if self.admission_generation.get() == 0
4184            || self.routing_revision.get() == 0
4185            || self.generation.get() == 0
4186            || self.fence.epoch().get() == 0
4187            || self.fence.epoch() != self.primary_term.epoch_component()
4188        {
4189            return Err(PinnedManifestBindingError::InvalidSemanticIdentity);
4190        }
4191        self.manifest
4192            .validate()
4193            .map_err(|error| PinnedManifestBindingError::InvalidObject(error.to_string()))?;
4194        self.root_set
4195            .validate()
4196            .map_err(|error| PinnedManifestBindingError::InvalidRootSet(error.to_string()))?;
4197        if self.manifest.physical.backend_authority != self.data_backend_authority
4198            || self.root_set.root.backend_authority != self.data_backend_authority
4199        {
4200            return Err(PinnedManifestBindingError::BackendAuthorityMismatch);
4201        }
4202        Ok(())
4203    }
4204}
4205
4206/// Stable identity of one exact committed vector-search cut.
4207///
4208/// The digest binds the logical index/mapping incarnation and every immutable
4209/// manifest/root visible to the vector branch. Callers must supply roots in
4210/// ascending shard order so the same cut has exactly one identity.
4211pub fn vector_snapshot_digest_v1(
4212    scope: CollectionIndexRef,
4213    schema_generation: SchemaGeneration,
4214    roots: &[PinnedManifestBindingV1],
4215) -> Result<[u8; 32], PinnedManifestBindingError> {
4216    if schema_generation.get() == 0
4217        || roots
4218            .iter()
4219            .any(|root| root.shard_key.collection_index_ref() != scope || root.validate().is_err())
4220        || roots
4221            .windows(2)
4222            .any(|pair| pair[0].shard_key.shard_id() >= pair[1].shard_key.shard_id())
4223    {
4224        return Err(PinnedManifestBindingError::InvalidSemanticIdentity);
4225    }
4226    let mut hasher = blake3::Hasher::new();
4227    hasher.update(b"lodestone-vector-snapshot-v1\0");
4228    hasher.update(&scope.collection_id().get().to_be_bytes());
4229    hasher.update(&scope.index_id().get().to_be_bytes());
4230    hasher.update(&schema_generation.get().to_be_bytes());
4231    let count = u32::try_from(roots.len()).map_err(|_| PinnedManifestBindingError::InvalidSemanticIdentity)?;
4232    hasher.update(&count.to_be_bytes());
4233    for root in roots {
4234        hasher.update(&root.shard_key.shard_id().get().to_be_bytes());
4235        hasher.update(&root.admission_generation.get().to_be_bytes());
4236        hasher.update(&root.routing_revision.get().to_be_bytes());
4237        hasher.update(&root.generation.get().to_be_bytes());
4238        hasher.update(&root.fence.epoch().get().to_be_bytes());
4239        hasher.update(&root.primary_term.get().to_be_bytes());
4240        hasher.update(&root.manifest.content_checksum);
4241        hasher.update(&root.root_set.content_digest);
4242        hasher.update(&root.committed_through.get().to_be_bytes());
4243    }
4244    Ok(*hasher.finalize().as_bytes())
4245}
4246
4247#[derive(Clone, Debug, Eq, PartialEq)]
4248pub enum PinnedManifestBindingError {
4249    UnsupportedVersion(u16),
4250    InvalidSemanticIdentity,
4251    InvalidObject(String),
4252    InvalidRootSet(String),
4253    BackendAuthorityMismatch,
4254}
4255
4256impl Display for PinnedManifestBindingError {
4257    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
4258        match self {
4259            Self::UnsupportedVersion(version) => {
4260                write!(formatter, "unsupported pinned manifest binding version {version}")
4261            }
4262            Self::InvalidSemanticIdentity => formatter.write_str("pinned manifest semantic identity is invalid"),
4263            Self::InvalidObject(reason) => write!(formatter, "pinned manifest object is invalid: {reason}"),
4264            Self::InvalidRootSet(reason) => write!(formatter, "pinned manifest root set is invalid: {reason}"),
4265            Self::BackendAuthorityMismatch => formatter.write_str("pinned manifest authorities do not match"),
4266        }
4267    }
4268}
4269
4270impl Error for PinnedManifestBindingError {}
4271
4272/// Identifier validation failure.
4273#[derive(Clone, Debug, Eq, PartialEq)]
4274pub enum IdentifierError {
4275    EmptyDocumentId,
4276    DocumentIdTooLong(usize),
4277    EmptyIdempotencyKey,
4278    IdempotencyKeyTooLong(usize),
4279    EmptyPitId,
4280    PitIdTooLong(usize),
4281    UnsupportedScopedPitIdVersion(u16),
4282    EmptyObjectKey,
4283    InvalidObjectKey(String),
4284}
4285
4286impl Display for IdentifierError {
4287    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
4288        match self {
4289            Self::EmptyDocumentId => formatter.write_str("document id must not be empty"),
4290            Self::DocumentIdTooLong(length) => {
4291                write!(formatter, "document id is {length} bytes; maximum is 512")
4292            }
4293            Self::EmptyIdempotencyKey => formatter.write_str("idempotency key must not be empty"),
4294            Self::IdempotencyKeyTooLong(length) => {
4295                write!(formatter, "idempotency key is {length} bytes; maximum is 256")
4296            }
4297            Self::EmptyPitId => formatter.write_str("PIT id must not be empty"),
4298            Self::PitIdTooLong(length) => {
4299                write!(formatter, "PIT id is {length} bytes; maximum is 512")
4300            }
4301            Self::UnsupportedScopedPitIdVersion(version) => {
4302                write!(formatter, "unsupported scoped PIT identifier version {version}")
4303            }
4304            Self::EmptyObjectKey => formatter.write_str("object key must not be empty"),
4305            Self::InvalidObjectKey(key) => {
4306                write!(formatter, "invalid relative object key '{key}'")
4307            }
4308        }
4309    }
4310}
4311
4312impl Error for IdentifierError {}
4313
4314/// Stable P0 routing hash stored as `_rh`.
4315#[must_use]
4316pub const fn routing_hash_v1(bytes: &[u8]) -> RoutingHash {
4317    let mut hash = 0x811c_9dc5_u32;
4318    let mut offset = 0;
4319    while offset < bytes.len() {
4320        hash ^= bytes[offset] as u32;
4321        hash = hash.wrapping_mul(0x0100_0193);
4322        offset += 1;
4323    }
4324    RoutingHash::new(hash)
4325}
4326
4327/// Stable P0 BLAKE3-derived mutable identity hash stored as two u64 fast fields.
4328#[must_use]
4329pub fn identity_hash_v1(bytes: &[u8]) -> IdHash {
4330    let digest = blake3::hash(bytes);
4331    let digest_bytes = digest.as_bytes();
4332    IdHash::new(u128::from_le_bytes([
4333        digest_bytes[0],
4334        digest_bytes[1],
4335        digest_bytes[2],
4336        digest_bytes[3],
4337        digest_bytes[4],
4338        digest_bytes[5],
4339        digest_bytes[6],
4340        digest_bytes[7],
4341        digest_bytes[8],
4342        digest_bytes[9],
4343        digest_bytes[10],
4344        digest_bytes[11],
4345        digest_bytes[12],
4346        digest_bytes[13],
4347        digest_bytes[14],
4348        digest_bytes[15],
4349    ]))
4350}
4351
4352#[cfg(test)]
4353mod policy_axis_tests {
4354    use super::{LifecycleVerb, StorageClass};
4355
4356    #[test]
4357    fn reserved_policy_axis_names_are_stable() {
4358        assert!(serde_json::to_string(&StorageClass::Standard).is_ok_and(|value| value == "\"standard\""));
4359        assert!(serde_json::to_string(&StorageClass::Cold).is_ok_and(|value| value == "\"cold\""));
4360        assert!(serde_json::to_string(&LifecycleVerb::Transition).is_ok_and(|value| value == "\"transition\""));
4361        assert!(
4362            serde_json::to_string(&LifecycleVerb::AggregateThenDelete)
4363                .is_ok_and(|value| value == "\"aggregate_then_delete\"")
4364        );
4365    }
4366}
4367
4368/// Computes CRC32C (Castagnoli) without platform-specific unsafe code.
4369#[must_use]
4370pub fn crc32c(bytes: &[u8]) -> u32 {
4371    let mut crc = u32::MAX;
4372    for byte in bytes {
4373        crc ^= u32::from(*byte);
4374        for _ in 0..8 {
4375            let mask = (crc & 1).wrapping_neg();
4376            crc = (crc >> 1) ^ (0x82f6_3b78 & mask);
4377        }
4378    }
4379    !crc
4380}
4381
4382/// A runtime role that can be composed into the Lodestone process.
4383#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
4384pub enum Role {
4385    Gateway,
4386    Indexer,
4387    Searcher,
4388    Compactor,
4389    Metastore,
4390}
4391
4392impl Role {
4393    /// All roles in stable display and startup order.
4394    pub const ALL: [Self; 5] = [
4395        Self::Gateway,
4396        Self::Indexer,
4397        Self::Searcher,
4398        Self::Compactor,
4399        Self::Metastore,
4400    ];
4401
4402    /// The stable CLI name of this role.
4403    #[must_use]
4404    pub const fn as_str(self) -> &'static str {
4405        match self {
4406            Self::Gateway => "gateway",
4407            Self::Indexer => "indexer",
4408            Self::Searcher => "searcher",
4409            Self::Compactor => "compactor",
4410            Self::Metastore => "metastore",
4411        }
4412    }
4413}
4414
4415impl Display for Role {
4416    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
4417        formatter.write_str(self.as_str())
4418    }
4419}
4420
4421impl FromStr for Role {
4422    type Err = RoleParseError;
4423
4424    fn from_str(value: &str) -> Result<Self, Self::Err> {
4425        match value {
4426            "gateway" => Ok(Self::Gateway),
4427            "indexer" => Ok(Self::Indexer),
4428            "searcher" => Ok(Self::Searcher),
4429            "compactor" => Ok(Self::Compactor),
4430            "metastore" => Ok(Self::Metastore),
4431            _ => Err(RoleParseError::Unknown(value.to_owned())),
4432        }
4433    }
4434}
4435
4436/// A validated, non-empty set of runtime roles.
4437#[derive(Clone, Debug, Eq, PartialEq)]
4438pub struct RoleSelection {
4439    roles: Vec<Role>,
4440}
4441
4442impl RoleSelection {
4443    /// Selects every runtime role.
4444    #[must_use]
4445    pub fn all() -> Self {
4446        Self {
4447            roles: Role::ALL.to_vec(),
4448        }
4449    }
4450
4451    /// Parses `all` or a comma-separated role list.
4452    pub fn parse(value: &str) -> Result<Self, RoleParseError> {
4453        if value == "all" {
4454            return Ok(Self::all());
4455        }
4456
4457        let mut roles = Vec::new();
4458        for candidate in value.split(',') {
4459            if candidate.is_empty() {
4460                return Err(RoleParseError::Empty);
4461            }
4462
4463            let role = candidate.parse::<Role>()?;
4464            if !roles.contains(&role) {
4465                roles.push(role);
4466            }
4467        }
4468
4469        if roles.is_empty() {
4470            return Err(RoleParseError::Empty);
4471        }
4472
4473        roles.sort_unstable();
4474        Ok(Self { roles })
4475    }
4476
4477    /// Returns roles in stable order.
4478    #[must_use]
4479    pub fn roles(&self) -> &[Role] {
4480        &self.roles
4481    }
4482
4483    /// Returns a stable comma-separated representation.
4484    #[must_use]
4485    pub fn display_list(&self) -> String {
4486        self.roles
4487            .iter()
4488            .map(|role| role.as_str())
4489            .collect::<Vec<_>>()
4490            .join(",")
4491    }
4492}
4493
4494impl Default for RoleSelection {
4495    fn default() -> Self {
4496        Self::all()
4497    }
4498}
4499
4500/// Failure to parse a runtime role selection.
4501#[derive(Clone, Debug, Eq, PartialEq)]
4502pub enum RoleParseError {
4503    Empty,
4504    Unknown(String),
4505}
4506
4507impl Display for RoleParseError {
4508    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
4509        match self {
4510            Self::Empty => formatter.write_str("role selection must not be empty"),
4511            Self::Unknown(role) => write!(
4512                formatter,
4513                "unknown role '{role}'; expected all, gateway, indexer, searcher, compactor, or metastore"
4514            ),
4515        }
4516    }
4517}
4518
4519impl Error for RoleParseError {}
4520
4521/// Arms a bounded external SIGKILL handshake at a named P1.5 durability
4522/// boundary.
4523///
4524/// The hook is inert unless all three `LOD_TEST_P15_FAULT_*` variables are
4525/// present. A live harness observes the ready file and kills the child; the
4526/// deadline prevents a misconfigured harness from wedging production code.
4527pub fn process_fault_point(point: &str) {
4528    process_fault_point_inner(point, None);
4529}
4530
4531/// Arms the same bounded process-fault handshake while attaching one small,
4532/// printable fact from the durable transition that reached the boundary.
4533///
4534/// Evidence is test-only observability, never protocol authority. Invalid or
4535/// oversized evidence refuses to publish the handshake so a harness fails by
4536/// timeout instead of accepting a truncated or ambiguous fact.
4537pub fn process_fault_point_with_evidence(point: &str, evidence: &str) {
4538    const MAX_EVIDENCE_BYTES: usize = 512;
4539    if evidence.is_empty()
4540        || evidence.len() > MAX_EVIDENCE_BYTES
4541        || !evidence
4542            .bytes()
4543            .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-' | b'=' | b'.'))
4544    {
4545        return;
4546    }
4547    process_fault_point_inner(point, Some(evidence));
4548}
4549
4550/// Test-only path receiving one exact P5.2 GC protection observation.
4551///
4552/// Unlike the process-fault handshake, this side channel never waits for a
4553/// release file. The Ready executor publishes it before advancing beyond the
4554/// observed protection boundary, and later fault hooks remain independently
4555/// armed.
4556pub const GC_PROTECTION_OBSERVATION_PATH_ENV: &str = "LOD_TEST_P52_GC_PROTECTION_OBSERVATION_PATH";
4557
4558/// Hard ceiling for one test-only GC protection observation.
4559pub const MAX_GC_PROTECTION_OBSERVATION_BYTES: usize = 8 * 1024 * 1024;
4560
4561/// Failure to publish one bounded test-only GC protection observation.
4562#[derive(Debug)]
4563pub enum GcProtectionObservationError {
4564    InvalidPath,
4565    Empty,
4566    TooLarge {
4567        actual: usize,
4568        maximum: usize,
4569    },
4570    AlreadyPublished(PathBuf),
4571    Io {
4572        operation: &'static str,
4573        path: PathBuf,
4574        source: std::io::Error,
4575    },
4576}
4577
4578impl Display for GcProtectionObservationError {
4579    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
4580        match self {
4581            Self::InvalidPath => formatter.write_str("GC protection observation path has no file name"),
4582            Self::Empty => formatter.write_str("GC protection observation is empty"),
4583            Self::TooLarge { actual, maximum } => write!(
4584                formatter,
4585                "GC protection observation is {actual} bytes; maximum is {maximum}"
4586            ),
4587            Self::AlreadyPublished(path) => {
4588                write!(
4589                    formatter,
4590                    "GC protection observation was already published at {}",
4591                    path.display()
4592                )
4593            }
4594            Self::Io {
4595                operation,
4596                path,
4597                source,
4598            } => write!(
4599                formatter,
4600                "GC protection observation {operation} failed for {}: {source}",
4601                path.display()
4602            ),
4603        }
4604    }
4605}
4606
4607impl Error for GcProtectionObservationError {
4608    fn source(&self) -> Option<&(dyn Error + 'static)> {
4609        match self {
4610            Self::Io { source, .. } => Some(source),
4611            _ => None,
4612        }
4613    }
4614}
4615
4616/// Returns the optional test-only observation destination.
4617#[must_use]
4618pub fn gc_protection_observation_path() -> Option<PathBuf> {
4619    std::env::var_os(GC_PROTECTION_OBSERVATION_PATH_ENV)
4620        .filter(|value| !value.is_empty())
4621        .map(PathBuf::from)
4622}
4623
4624/// Atomically publishes one bounded observation without replacing an older
4625/// one.
4626///
4627/// Callers must execute this blocking filesystem helper on a bounded blocking
4628/// pool. The completed temporary file is linked into place only after every
4629/// byte is written and synced, so readers never observe a partial record.
4630pub fn write_gc_protection_observation(path: &Path, encoded: &[u8]) -> Result<(), GcProtectionObservationError> {
4631    if encoded.is_empty() {
4632        return Err(GcProtectionObservationError::Empty);
4633    }
4634    if encoded.len() > MAX_GC_PROTECTION_OBSERVATION_BYTES {
4635        return Err(GcProtectionObservationError::TooLarge {
4636            actual: encoded.len(),
4637            maximum: MAX_GC_PROTECTION_OBSERVATION_BYTES,
4638        });
4639    }
4640    let file_name = path
4641        .file_name()
4642        .and_then(|name| name.to_str())
4643        .filter(|name| !name.is_empty())
4644        .ok_or(GcProtectionObservationError::InvalidPath)?;
4645    let temporary = path.with_file_name(format!(".{file_name}.{}.tmp", std::process::id()));
4646    let mut file = std::fs::OpenOptions::new()
4647        .write(true)
4648        .create_new(true)
4649        .open(&temporary)
4650        .map_err(|source| GcProtectionObservationError::Io {
4651            operation: "temporary-file creation",
4652            path: temporary.clone(),
4653            source,
4654        })?;
4655    let write_result = file.write_all(encoded).and_then(|()| file.sync_all());
4656    drop(file);
4657    if let Err(source) = write_result {
4658        let _cleanup_result = std::fs::remove_file(&temporary);
4659        return Err(GcProtectionObservationError::Io {
4660            operation: "temporary-file write",
4661            path: temporary,
4662            source,
4663        });
4664    }
4665    if let Err(source) = std::fs::hard_link(&temporary, path) {
4666        let _cleanup_result = std::fs::remove_file(&temporary);
4667        if source.kind() == std::io::ErrorKind::AlreadyExists {
4668            return Err(GcProtectionObservationError::AlreadyPublished(path.to_path_buf()));
4669        }
4670        return Err(GcProtectionObservationError::Io {
4671            operation: "atomic publication",
4672            path: path.to_path_buf(),
4673            source,
4674        });
4675    }
4676    std::fs::remove_file(&temporary).map_err(|source| GcProtectionObservationError::Io {
4677        operation: "temporary-file cleanup",
4678        path: temporary,
4679        source,
4680    })?;
4681    Ok(())
4682}
4683
4684fn process_fault_point_inner(point: &str, evidence: Option<&str>) {
4685    struct FaultConfig {
4686        point: String,
4687        ready_path: PathBuf,
4688        release_path: PathBuf,
4689        timeout: Duration,
4690    }
4691    static CONFIG: OnceLock<Option<FaultConfig>> = OnceLock::new();
4692    let config = CONFIG.get_or_init(|| {
4693        let point = std::env::var("LOD_TEST_LISTENER_FAULT_POINT").ok()?;
4694        let ready_path = std::env::var_os("LOD_TEST_LISTENER_FAULT_READY").map(PathBuf::from)?;
4695        let release_path = std::env::var_os("LOD_TEST_LISTENER_FAULT_RELEASE").map(PathBuf::from)?;
4696        let timeout_millis = std::env::var("LOD_TEST_LISTENER_FAULT_TIMEOUT_MILLIS")
4697            .ok()
4698            .and_then(|value| value.parse::<u64>().ok())
4699            .unwrap_or(30_000)
4700            .min(60_000);
4701        Some(FaultConfig {
4702            point,
4703            ready_path,
4704            release_path,
4705            timeout: Duration::from_millis(timeout_millis),
4706        })
4707    });
4708    let Some(config) = config.as_ref().filter(|config| config.point == point) else {
4709        return;
4710    };
4711    let payload = evidence.map_or_else(|| point.to_owned(), |fact| format!("{point}\n{fact}"));
4712    if std::fs::write(&config.ready_path, payload.as_bytes()).is_err() {
4713        return;
4714    }
4715    let deadline = Instant::now() + config.timeout;
4716    while !config.release_path.exists() && Instant::now() < deadline {
4717        std::thread::yield_now();
4718    }
4719}
4720
4721#[cfg(test)]
4722mod hash_backend_tests;
4723
4724#[cfg(test)]
4725mod tests {
4726    use std::collections::BTreeMap;
4727    use std::sync::atomic::{AtomicU64, Ordering};
4728
4729    use super::{
4730        CollectionId, CollectionIndexRef, DocumentId, DurabilityClass, DynamicMappingProposalError,
4731        DynamicMappingProposalV1, Epoch, FieldMapping, FieldType, GcProtectionObservationError, IndexId, IndexMode,
4732        MAPPING_FORMAT_VERSION, MAX_GC_PROTECTION_OBSERVATION_BYTES, MAX_MAPPING_FIELD_NAME_BYTES, MAX_MAPPING_FIELDS,
4733        ManifestGeneration, Mapping, MappingExtensions, MappingScalar, MappingValidationError, ObjectKey, PitId, Role,
4734        RoleSelection, RoutingHash, RoutingRangeEnd, SchemaGeneration, ScopedPitId, SeqNo, ShardDescriptor, ShardId,
4735        TaskId, VisibilityMode, crc32c, validate_mapping_field_name, write_gc_protection_observation,
4736    };
4737
4738    static OBSERVATION_TEST_SEQUENCE: AtomicU64 = AtomicU64::new(0);
4739
4740    #[test]
4741    fn object_key_memory_charge_preserves_spare_string_capacity() -> Result<(), Box<dyn std::error::Error>> {
4742        let mut value = String::with_capacity(4096);
4743        value.push_str("a/b");
4744        let capacity = value.capacity();
4745        let key = ObjectKey::new(value)?;
4746        assert_eq!(key.as_str(), "a/b");
4747        assert_eq!(key.allocated_capacity_bytes(), capacity);
4748        assert!(key.allocated_capacity_bytes() > key.as_str().len());
4749        assert_eq!(serde_json::to_string(&key)?, "\"a/b\"");
4750        Ok(())
4751    }
4752
4753    #[test]
4754    fn all_expands_to_every_role() {
4755        let selection = RoleSelection::parse("all");
4756
4757        assert_eq!(selection.as_ref().map(|value| value.roles()), Ok(Role::ALL.as_slice()));
4758    }
4759
4760    #[test]
4761    fn explicit_roles_are_deduplicated_and_stable() {
4762        let selection = RoleSelection::parse("searcher,gateway,searcher");
4763
4764        assert_eq!(
4765            selection.as_ref().map(|value| value.roles()),
4766            Ok([Role::Gateway, Role::Searcher].as_slice())
4767        );
4768    }
4769
4770    #[test]
4771    fn unknown_role_is_rejected() {
4772        let selection = RoleSelection::parse("unknown");
4773
4774        assert!(selection.is_err());
4775    }
4776
4777    #[test]
4778    fn semantic_numbers_advance_without_crossing_types() {
4779        assert_eq!(Epoch::new(7).checked_next(), Some(Epoch::new(8)));
4780        assert_eq!(SeqNo::new(41).checked_next(), Some(SeqNo::new(42)));
4781        assert_eq!(ManifestGeneration::new(u64::MAX).checked_next(), None);
4782    }
4783
4784    #[test]
4785    fn sparse_values_are_canonical_bounded_and_mapping_typed() -> Result<(), Box<dyn std::error::Error>> {
4786        let definition = super::SparseVectorDefinition {
4787            maximum_dimension: 12,
4788            weight_type: super::SparseVectorWeightType::F16,
4789        };
4790        let value = super::SparseVectorValue {
4791            entries: vec![
4792                super::SparseVectorEntry {
4793                    dimension: 1,
4794                    weight: 1.5,
4795                },
4796                super::SparseVectorEntry {
4797                    dimension: 9,
4798                    weight: -2.0,
4799                },
4800            ],
4801        };
4802        let decoded = value.decode(definition)?;
4803        let native = value.canonical_native_bytes(definition)?;
4804        assert_eq!(super::decode_sparse_native_bytes(definition, &native)?, decoded);
4805        assert_eq!(native.len(), 12);
4806        let encoded = serde_json::to_vec(&value)?;
4807        assert_eq!(serde_json::from_slice::<super::SparseVectorValue>(&encoded)?, value);
4808        #[derive(Debug, serde::Deserialize, PartialEq, serde::Serialize)]
4809        #[serde(tag = "kind", rename_all = "snake_case")]
4810        enum TaggedSparse {
4811            Sparse {
4812                query_vector: super::SparseVectorValue,
4813                #[serde(deserialize_with = "super::deserialize_sparse_weight")]
4814                weight: f64,
4815            },
4816        }
4817        let tagged = TaggedSparse::Sparse {
4818            query_vector: value.clone(),
4819            weight: 0.5,
4820        };
4821        let encoded = serde_json::to_vec(&tagged)?;
4822        assert_eq!(serde_json::from_slice::<TaggedSparse>(&encoded)?, tagged);
4823
4824        for invalid in [
4825            super::SparseVectorValue {
4826                entries: vec![
4827                    super::SparseVectorEntry {
4828                        dimension: 1,
4829                        weight: 1.0,
4830                    },
4831                    super::SparseVectorEntry {
4832                        dimension: 1,
4833                        weight: 2.0,
4834                    },
4835                ],
4836            },
4837            super::SparseVectorValue {
4838                entries: vec![super::SparseVectorEntry {
4839                    dimension: 2,
4840                    weight: 0.0,
4841                }],
4842            },
4843            super::SparseVectorValue {
4844                entries: vec![super::SparseVectorEntry {
4845                    dimension: 13,
4846                    weight: 1.0,
4847                }],
4848            },
4849            super::SparseVectorValue {
4850                entries: vec![super::SparseVectorEntry {
4851                    dimension: 2,
4852                    weight: f64::NAN,
4853                }],
4854            },
4855        ] {
4856            assert!(invalid.decode(definition).is_err());
4857        }
4858        Ok(())
4859    }
4860
4861    #[test]
4862    fn vector_set_values_are_explicit_typed_and_bounded() -> Result<(), Box<dyn std::error::Error>> {
4863        let definition = super::VectorSetDefinition {
4864            dense: super::DenseVectorDefinition {
4865                dimensions: 2,
4866                element_type: super::DenseVectorElementType::F32,
4867                metric: super::VectorMetric::DotProduct,
4868                quantization_scale: None,
4869            },
4870            maximum_vectors: 2,
4871        };
4872        let value = super::VectorSetValue {
4873            vectors: vec![
4874                super::DenseVectorValue::Numeric { values: vec![1.0, 0.0] },
4875                super::DenseVectorValue::Numeric { values: vec![0.0, 1.0] },
4876            ],
4877        };
4878        let decoded = value.decode(
4879            definition,
4880            super::MAX_VECTOR_SET_DOCUMENT_VECTORS,
4881            super::MAX_VECTOR_SET_BYTES_PER_FIELD,
4882        )?;
4883        assert_eq!(decoded.vectors, vec![vec![1.0, 0.0], vec![0.0, 1.0]]);
4884        assert_eq!(decoded.native_bytes, 16);
4885        assert!(
4886            super::VectorSetValue { vectors: Vec::new() }
4887                .decode(definition, 2, super::MAX_VECTOR_SET_BYTES_PER_FIELD)
4888                .is_err()
4889        );
4890        assert!(
4891            super::VectorSetValue {
4892                vectors: vec![super::DenseVectorValue::Numeric { values: vec![1.0, 0.0] }; 3],
4893            }
4894            .decode(definition, 2, super::MAX_VECTOR_SET_BYTES_PER_FIELD)
4895            .is_err()
4896        );
4897        assert!(
4898            super::VectorSetValue {
4899                vectors: vec![super::DenseVectorValue::Numeric { values: vec![1.0] }],
4900            }
4901            .decode(definition, 2, super::MAX_VECTOR_SET_BYTES_PER_FIELD)
4902            .is_err()
4903        );
4904        let oversized_json = serde_json::json!({
4905            "vectors": (0..=super::MAX_VECTOR_SET_DOCUMENT_VECTORS)
4906                .map(|_| serde_json::json!({"kind": "numeric", "values": [1.0, 0.0]}))
4907                .collect::<Vec<_>>()
4908        });
4909        assert!(serde_json::from_value::<super::VectorSetValue>(oversized_json).is_err());
4910        Ok(())
4911    }
4912
4913    #[test]
4914    fn task_ids_round_trip_the_complete_u128_domain_and_reject_numbers() {
4915        let task_id = TaskId::new(u128::MAX - 7);
4916        let encoded = serde_json::to_string(&task_id);
4917        let expected = format!("\"{}\"", u128::MAX - 7);
4918        assert_eq!(encoded.as_ref().ok().map(String::as_str), Some(expected.as_str()));
4919        assert_eq!(
4920            encoded
4921                .as_deref()
4922                .ok()
4923                .and_then(|encoded| serde_json::from_str(encoded).ok()),
4924            Some(task_id)
4925        );
4926        assert!(serde_json::from_str::<TaskId>("41").is_err());
4927    }
4928
4929    #[test]
4930    fn gc_protection_observation_writer_is_bounded_atomic_and_single_assignment()
4931    -> Result<(), Box<dyn std::error::Error>> {
4932        let sequence = OBSERVATION_TEST_SEQUENCE.fetch_add(1, Ordering::Relaxed);
4933        let root = std::env::temp_dir().join(format!(
4934            "lodestone-p52-gc-protection-observation-{}-{sequence}",
4935            std::process::id()
4936        ));
4937        std::fs::create_dir(&root)?;
4938        let path = root.join("applied.json");
4939        let encoded = br#"{"format_version":1}"#;
4940
4941        write_gc_protection_observation(&path, encoded)?;
4942        assert_eq!(std::fs::read(&path)?, encoded);
4943        assert!(matches!(
4944            write_gc_protection_observation(&path, encoded),
4945            Err(GcProtectionObservationError::AlreadyPublished(actual)) if actual == path
4946        ));
4947        assert!(matches!(
4948            write_gc_protection_observation(
4949                &root.join("oversized.json"),
4950                &vec![0; MAX_GC_PROTECTION_OBSERVATION_BYTES.saturating_add(1)]
4951            ),
4952            Err(GcProtectionObservationError::TooLarge { .. })
4953        ));
4954        assert!(!root.join("oversized.json").exists());
4955
4956        std::fs::remove_dir_all(root)?;
4957        Ok(())
4958    }
4959
4960    #[test]
4961    fn durable_identifiers_reject_ambiguous_values() {
4962        assert!(DocumentId::new("").is_err());
4963        assert!(PitId::new("").is_err());
4964        assert!(PitId::new("opaque-pit-lease").is_ok());
4965        assert!(ObjectKey::new("../escape").is_err());
4966        assert!(ObjectKey::new("valid/prefix/object").is_ok());
4967    }
4968
4969    #[test]
4970    fn scoped_pit_id_round_trips_and_rejects_newer_formats() -> Result<(), Box<dyn std::error::Error>> {
4971        let pit_id = ScopedPitId::new(super::RecoveryEra::new(7)?, PitId::new("opaque-pit-lease")?);
4972        let encoded = serde_json::to_vec(&pit_id)?;
4973        assert_eq!(serde_json::from_slice::<ScopedPitId>(&encoded)?, pit_id);
4974
4975        let mut unsupported: serde_json::Value = serde_json::from_slice(&encoded)?;
4976        let Some(object) = unsupported.as_object_mut() else {
4977            return Err("scoped PIT identifier was not an object".into());
4978        };
4979        object.insert("format_version".to_owned(), serde_json::json!(2));
4980        assert!(serde_json::from_value::<ScopedPitId>(unsupported).is_err());
4981        Ok(())
4982    }
4983
4984    #[test]
4985    fn crc32c_matches_the_standard_check_value() {
4986        assert_eq!(crc32c(b"123456789"), 0xe306_9283);
4987    }
4988
4989    #[test]
4990    fn durability_names_round_trip_and_admitted_classes_are_available() {
4991        let classes = [
4992            DurabilityClass::RegionalObject,
4993            DurabilityClass::ZonalLowLatency,
4994            DurabilityClass::Relaxed,
4995            DurabilityClass::Ephemeral,
4996        ];
4997        for class in classes {
4998            let encoded = serde_json::to_string(&class);
4999            assert_eq!(
5000                encoded.as_ref().ok().and_then(|bytes| serde_json::from_str(bytes).ok()),
5001                Some(class)
5002            );
5003        }
5004        assert!(DurabilityClass::RegionalObject.require_available().is_ok());
5005        assert!(DurabilityClass::ZonalLowLatency.require_available().is_ok());
5006        assert!(DurabilityClass::Relaxed.require_available().is_err());
5007        assert!(DurabilityClass::Ephemeral.require_available().is_err());
5008        assert!(DurabilityClass::RegionalObject.require_state_available().is_ok());
5009        assert!(DurabilityClass::ZonalLowLatency.require_state_available().is_ok());
5010        assert!(DurabilityClass::Ephemeral.require_state_available().is_ok());
5011        assert!(DurabilityClass::Relaxed.require_state_available().is_err());
5012        assert!(
5013            serde_json::to_string(&super::DurabilityBoundary::IncarnationMemory)
5014                .is_ok_and(|encoded| encoded == "\"incarnation_memory\"")
5015        );
5016        assert!(serde_json::from_str::<DurabilityClass>("\"future\"").is_err());
5017        assert!(matches!(
5018            serde_json::from_str::<VisibilityMode>("\"wait_for\""),
5019            Ok(VisibilityMode::WaitFor)
5020        ));
5021    }
5022
5023    #[test]
5024    fn shard_descriptor_covers_exactly_its_hash_range() {
5025        let descriptor = ShardDescriptor::new(
5026            ShardId::new(7),
5027            Epoch::new(3),
5028            RoutingHash::new(10),
5029            RoutingRangeEnd::new(20).unwrap_or(RoutingRangeEnd::FULL),
5030        );
5031        assert!(descriptor.is_ok());
5032        let descriptor = descriptor.unwrap_or(ShardDescriptor {
5033            shard_id: ShardId::new(7),
5034            epoch: Epoch::new(3),
5035            hash_start: RoutingHash::new(10),
5036            hash_end: RoutingRangeEnd::FULL,
5037        });
5038        assert!(!descriptor.contains(RoutingHash::new(9)));
5039        assert!(descriptor.contains(RoutingHash::new(10)));
5040        assert!(descriptor.contains(RoutingHash::new(19)));
5041        assert!(!descriptor.contains(RoutingHash::new(20)));
5042    }
5043
5044    #[test]
5045    fn shared_mapping_model_validates_semantics_and_refuses_retired_versions_and_unknown_fields()
5046    -> Result<(), Box<dyn std::error::Error>> {
5047        let mapping = Mapping {
5048            format_version: MAPPING_FORMAT_VERSION,
5049            schema_generation: SchemaGeneration::new(1),
5050            mode: IndexMode::Mutable,
5051            fields: BTreeMap::from([(
5052                "message".to_owned(),
5053                FieldMapping {
5054                    field_type: FieldType::Text,
5055                    stored: false,
5056                    null_value: None,
5057                },
5058            )]),
5059            extensions: crate::MappingExtensions::default(),
5060        };
5061        assert_eq!(mapping.validate(), Ok(()));
5062        assert_eq!(mapping.validate_current(), Ok(()));
5063        let canonical = serde_json::to_value(&mapping)?;
5064        for retired_version in 1..MAPPING_FORMAT_VERSION {
5065            let retired = Mapping {
5066                format_version: retired_version,
5067                ..mapping.clone()
5068            };
5069            assert_eq!(
5070                retired.validate(),
5071                Err(MappingValidationError::UnsupportedVersion(retired_version))
5072            );
5073            assert!(matches!(
5074                retired.encode_canonical(),
5075                Err(super::MappingCodecError::Invalid(
5076                    MappingValidationError::UnsupportedVersion(version)
5077                )) if version == retired_version
5078            ));
5079
5080            let mut retired_bytes = canonical.clone();
5081            retired_bytes["format_version"] = serde_json::json!(retired_version);
5082            let retired_bytes = serde_json::to_vec(&retired_bytes)?;
5083            let original = retired_bytes.clone();
5084            assert!(matches!(
5085                Mapping::decode_canonical(&retired_bytes),
5086                Err(super::MappingCodecError::UnsupportedVersion(version)) if version == retired_version
5087            ));
5088            assert_eq!(retired_bytes, original);
5089            assert!(serde_json::from_slice::<Mapping>(&retired_bytes).is_err());
5090        }
5091
5092        let future = Mapping {
5093            format_version: MAPPING_FORMAT_VERSION + 1,
5094            ..mapping.clone()
5095        };
5096        assert_eq!(
5097            future.validate(),
5098            Err(MappingValidationError::UnsupportedVersion(MAPPING_FORMAT_VERSION + 1))
5099        );
5100
5101        let invalid = Mapping {
5102            schema_generation: SchemaGeneration::new(0),
5103            ..mapping.clone()
5104        };
5105        assert_eq!(invalid.validate(), Err(MappingValidationError::ZeroSchemaGeneration));
5106        assert!(
5107            serde_json::from_str::<Mapping>(
5108                r#"{"format_version":1,"schema_generation":1,"mode":"mutable","fields":{},"future":true}"#
5109            )
5110            .is_err()
5111        );
5112        for invalid in [".message", "message.", "object..message"] {
5113            let mut invalid_path = mapping.clone();
5114            invalid_path.fields = BTreeMap::from([(
5115                invalid.to_owned(),
5116                FieldMapping {
5117                    field_type: FieldType::Text,
5118                    stored: false,
5119                    null_value: None,
5120                },
5121            )]);
5122            assert_eq!(
5123                invalid_path.validate(),
5124                Err(MappingValidationError::InvalidFieldName(invalid.to_owned()))
5125            );
5126        }
5127        for (ancestor, descendant) in [("a", "a.b"), ("a.b", "a.b.c")] {
5128            let mut conflicting = mapping.clone();
5129            conflicting.fields = BTreeMap::from([
5130                (
5131                    ancestor.to_owned(),
5132                    FieldMapping {
5133                        field_type: FieldType::Keyword,
5134                        stored: false,
5135                        null_value: None,
5136                    },
5137                ),
5138                (
5139                    descendant.to_owned(),
5140                    FieldMapping {
5141                        field_type: FieldType::U64,
5142                        stored: false,
5143                        null_value: None,
5144                    },
5145                ),
5146            ]);
5147            assert_eq!(
5148                conflicting.validate(),
5149                Err(MappingValidationError::ConflictingFieldPaths {
5150                    ancestor: ancestor.to_owned(),
5151                    descendant: descendant.to_owned(),
5152                })
5153            );
5154        }
5155        let mut siblings = mapping;
5156        siblings.fields = BTreeMap::from([
5157            (
5158                "a.b".to_owned(),
5159                FieldMapping {
5160                    field_type: FieldType::Keyword,
5161                    stored: false,
5162                    null_value: None,
5163                },
5164            ),
5165            (
5166                "a.bc".to_owned(),
5167                FieldMapping {
5168                    field_type: FieldType::U64,
5169                    stored: false,
5170                    null_value: None,
5171                },
5172            ),
5173        ]);
5174        assert_eq!(siblings.validate(), Ok(()));
5175        Ok(())
5176    }
5177
5178    #[test]
5179    fn ec6_mapping_refuses_nested_paths_below_nested_paths() {
5180        let nested = || FieldMapping {
5181            field_type: FieldType::Nested,
5182            stored: false,
5183            null_value: None,
5184        };
5185        let mapping = Mapping {
5186            format_version: MAPPING_FORMAT_VERSION,
5187            schema_generation: SchemaGeneration::new(1),
5188            mode: IndexMode::Mutable,
5189            fields: BTreeMap::from([
5190                ("items".to_owned(), nested()),
5191                ("items-options".to_owned(), nested()),
5192                ("items.options".to_owned(), nested()),
5193            ]),
5194            extensions: MappingExtensions {
5195                nested_paths: vec![
5196                    "items".to_owned(),
5197                    "items-options".to_owned(),
5198                    "items.options".to_owned(),
5199                ],
5200                ..MappingExtensions::default()
5201            },
5202        };
5203        assert_eq!(
5204            mapping.validate(),
5205            Err(MappingValidationError::NestedPathOverlap {
5206                parent: "items".to_owned(),
5207                child: "items.options".to_owned(),
5208            })
5209        );
5210
5211        let mut predecessor = Mapping::empty(IndexMode::Mutable);
5212        predecessor.fields.insert(
5213            "items.color".to_owned(),
5214            FieldMapping {
5215                field_type: FieldType::Keyword,
5216                stored: false,
5217                null_value: None,
5218            },
5219        );
5220        let mut successor = predecessor.clone();
5221        successor.schema_generation = SchemaGeneration::new(2);
5222        successor.fields.insert("items".to_owned(), nested());
5223        successor.extensions.nested_paths = vec!["items".to_owned()];
5224        assert!(!successor.is_additive_successor_of(&predecessor));
5225
5226        let mut safe_successor = predecessor.clone();
5227        safe_successor.schema_generation = SchemaGeneration::new(2);
5228        safe_successor.fields.insert("other".to_owned(), nested());
5229        safe_successor.fields.insert(
5230            "other.color".to_owned(),
5231            FieldMapping {
5232                field_type: FieldType::Keyword,
5233                stored: false,
5234                null_value: None,
5235            },
5236        );
5237        safe_successor.extensions.nested_paths = vec!["other".to_owned()];
5238        assert!(safe_successor.is_additive_successor_of(&predecessor));
5239    }
5240
5241    #[test]
5242    fn ec5_dynamic_mapping_proposal_normalizes_signed_zero_and_refuses_noncanonical_decode()
5243    -> Result<(), Box<dyn std::error::Error>> {
5244        let proposal = DynamicMappingProposalV1::new(
5245            CollectionIndexRef::new(CollectionId::new(7)?, IndexId::new(3)?),
5246            SchemaGeneration::new(1),
5247            "temperature".to_owned(),
5248            MappingScalar::F64Bits((-0.0_f64).to_bits()),
5249        )?;
5250        assert_eq!(
5251            proposal.first_non_null_candidate,
5252            MappingScalar::F64Bits(0.0_f64.to_bits())
5253        );
5254
5255        let mut noncanonical = proposal;
5256        noncanonical.first_non_null_candidate = MappingScalar::F64Bits((-0.0_f64).to_bits());
5257        noncanonical.proposal_digest = noncanonical.exact_digest()?;
5258        let encoded = serde_json::to_vec(&noncanonical)?;
5259        assert!(matches!(
5260            DynamicMappingProposalV1::decode_canonical(&encoded),
5261            Err(DynamicMappingProposalError::InvalidCandidate)
5262        ));
5263        Ok(())
5264    }
5265
5266    #[test]
5267    fn ec5_deployment_mapping_fixtures_use_the_current_validated_format() -> Result<(), Box<dyn std::error::Error>> {
5268        for encoded in [
5269            include_str!("../../../fixtures/runtime/secure-strict-mapping.json"),
5270            include_str!("../../../fixtures/performance/search-mapping.json"),
5271            include_str!("../../../fixtures/reindex/mapping.json"),
5272            include_str!("../../../tests/performance/es-comparison-mapping-v1.json"),
5273        ] {
5274            let mapping: Mapping = serde_json::from_str(encoded)?;
5275            mapping.validate_current()?;
5276            assert_eq!(mapping.schema_generation, SchemaGeneration::new(1));
5277        }
5278        Ok(())
5279    }
5280
5281    #[test]
5282    fn public_replayable_source_mapping_contract_matches_current_model() -> Result<(), serde_json::Error> {
5283        let contract: serde_json::Value = serde_json::from_str(include_str!(
5284            "../../../public/development/replayable-source-mapping-v1.json"
5285        ))?;
5286        assert_eq!(contract["mapping"]["format_version"], MAPPING_FORMAT_VERSION);
5287        assert_eq!(contract["mapping"]["maximum_fields"], MAX_MAPPING_FIELDS);
5288        assert_eq!(
5289            contract["mapping"]["maximum_field_name_bytes"],
5290            MAX_MAPPING_FIELD_NAME_BYTES
5291        );
5292        assert_eq!(
5293            contract["mapping"]["scalar_field_types"],
5294            serde_json::json!(["text", "keyword", "u64", "date", "i64", "f64", "bool"])
5295        );
5296
5297        let mapping = Mapping {
5298            format_version: MAPPING_FORMAT_VERSION,
5299            schema_generation: SchemaGeneration::new(1),
5300            mode: IndexMode::AppendLog,
5301            fields: BTreeMap::from([
5302                (
5303                    "message".to_owned(),
5304                    FieldMapping {
5305                        field_type: FieldType::Text,
5306                        stored: false,
5307                        null_value: None,
5308                    },
5309                ),
5310                (
5311                    "record_id".to_owned(),
5312                    FieldMapping {
5313                        field_type: FieldType::Keyword,
5314                        stored: false,
5315                        null_value: None,
5316                    },
5317                ),
5318            ]),
5319            extensions: crate::MappingExtensions::default(),
5320        };
5321        assert_eq!(
5322            serde_json::Value::String(serde_json::to_string(&mapping)?),
5323            contract["example"]
5324        );
5325        for invalid in ["", ".message", "message.", "object..message", "_reserved", "a\u{7f}b"] {
5326            assert_eq!(
5327                validate_mapping_field_name(invalid),
5328                Err(MappingValidationError::InvalidFieldName(invalid.to_owned()))
5329            );
5330        }
5331        Ok(())
5332    }
5333
5334    #[test]
5335    fn shared_field_path_resolver_uses_canonical_nested_components() {
5336        #[derive(Default)]
5337        struct Node {
5338            children: BTreeMap<&'static str, Node>,
5339            value: Option<u64>,
5340        }
5341
5342        let root = Node {
5343            children: BTreeMap::from([(
5344                "metrics",
5345                Node {
5346                    children: BTreeMap::from([(
5347                        "count",
5348                        Node {
5349                            children: BTreeMap::new(),
5350                            value: Some(7),
5351                        },
5352                    )]),
5353                    value: None,
5354                },
5355            )]),
5356            value: None,
5357        };
5358        assert_eq!(
5359            super::resolve_field_path(&root, "metrics.count", |node, component| node.children.get(component))
5360                .and_then(|node| node.value),
5361            Some(7)
5362        );
5363        assert!(
5364            super::resolve_field_path(&root, "metrics..count", |node, component| node.children.get(component))
5365                .is_none()
5366        );
5367    }
5368
5369    #[test]
5370    fn root_set_reference_is_exact_bounded_and_canonical() -> Result<(), Box<dyn std::error::Error>> {
5371        let root = super::RootSetRef {
5372            format_version: super::ROOT_SET_REF_FORMAT_VERSION,
5373            root: super::PhysicalObjectId {
5374                backend_authority: super::BackendAuthorityId::new("standard-data")?,
5375                object_incarnation: super::ObjectIncarnation::new(9)?,
5376                object_key: super::ObjectKey::new("root-sets/0009.json")?,
5377            },
5378            content_digest: [7; 32],
5379            item_count: 4,
5380            page_count: 2,
5381            schema_generations: vec![super::SchemaGeneration::new(1), super::SchemaGeneration::new(3)],
5382        };
5383        root.validate()?;
5384        assert_eq!(
5385            serde_json::from_slice::<super::RootSetRef>(&serde_json::to_vec(&root)?)?,
5386            root
5387        );
5388
5389        let mut duplicate = root.clone();
5390        duplicate.schema_generations = vec![super::SchemaGeneration::new(1), super::SchemaGeneration::new(1)];
5391        assert_eq!(
5392            duplicate.validate(),
5393            Err(super::RootSetRefError::InvalidSchemaGenerations)
5394        );
5395        let mut empty = root;
5396        empty.item_count = 0;
5397        assert_eq!(
5398            empty.validate(),
5399            Err(super::RootSetRefError::InvalidCounts { items: 0, pages: 2 })
5400        );
5401        let mut single_page = empty;
5402        single_page.item_count = 1;
5403        single_page.page_count = 1;
5404        single_page.validate()?;
5405        let mut too_many_pages = single_page.clone();
5406        too_many_pages.page_count = 2;
5407        assert_eq!(
5408            too_many_pages.validate(),
5409            Err(super::RootSetRefError::InvalidCounts { items: 1, pages: 2 })
5410        );
5411        let mut predecessor = single_page;
5412        predecessor.format_version = 1;
5413        assert_eq!(
5414            predecessor.validate(),
5415            Err(super::RootSetRefError::UnsupportedVersion(1))
5416        );
5417        Ok(())
5418    }
5419
5420    #[test]
5421    fn kubernetes_object_uid_digest_refuses_zero_on_construction_and_decode() -> Result<(), Box<dyn std::error::Error>>
5422    {
5423        assert!(super::KubernetesObjectUidDigest::new([0; 32]).is_err());
5424        assert!(
5425            serde_json::from_str::<super::KubernetesObjectUidDigest>(&format!("[{}]", "0,".repeat(31) + "0")).is_err()
5426        );
5427        let digest = super::KubernetesObjectUidDigest::new([7; 32])?;
5428        let decoded: super::KubernetesObjectUidDigest = serde_json::from_slice(&serde_json::to_vec(&digest)?)?;
5429        assert_eq!(decoded, digest);
5430        Ok(())
5431    }
5432
5433    #[test]
5434    fn gateway_d1_semantic_identities_and_listener_profiles_are_exact() -> Result<(), Box<dyn std::error::Error>> {
5435        assert!(super::GatewayIncarnation::new([0; 32]).is_err());
5436        assert!(serde_json::from_str::<super::GatewayIncarnation>(&format!("[{}]", "0,".repeat(31) + "0")).is_err());
5437        let incarnation = super::GatewayIncarnation::new([7; 32])?;
5438        assert_eq!(
5439            serde_json::from_slice::<super::GatewayIncarnation>(&serde_json::to_vec(&incarnation)?)?,
5440            incarnation
5441        );
5442        assert_eq!(super::AuditRangeId::new(4).get(), 4);
5443        assert_eq!(super::QuotaGrantId::new(5).get(), 5);
5444        assert_eq!(super::QuotaWindow::new(6).get(), 6);
5445        assert_eq!(super::ProcessMonotonicMillis::new(7).get(), 7);
5446        assert_eq!(super::UnixMillis::new(8).get(), 8);
5447        for (listener, profile) in [
5448            (
5449                super::PublicGatewayListenerV1::LodestoneApi,
5450                super::PublicTlsProfileV1::Native,
5451            ),
5452            (
5453                super::PublicGatewayListenerV1::ElasticsearchHttp,
5454                super::PublicTlsProfileV1::Http,
5455            ),
5456            (
5457                super::PublicGatewayListenerV1::OtlpHttp,
5458                super::PublicTlsProfileV1::Http,
5459            ),
5460            (
5461                super::PublicGatewayListenerV1::JaegerHttp,
5462                super::PublicTlsProfileV1::Http,
5463            ),
5464            (
5465                super::PublicGatewayListenerV1::OtlpGrpc,
5466                super::PublicTlsProfileV1::Grpc,
5467            ),
5468            (
5469                super::PublicGatewayListenerV1::SubscriptionGrpc,
5470                super::PublicTlsProfileV1::Grpc,
5471            ),
5472            (
5473                super::PublicGatewayListenerV1::SubscriptionWebSocket,
5474                super::PublicTlsProfileV1::Http,
5475            ),
5476            (super::PublicGatewayListenerV1::Resp, super::PublicTlsProfileV1::Resp),
5477        ] {
5478            assert_eq!(listener.required_tls_profile(), profile);
5479        }
5480        Ok(())
5481    }
5482
5483    #[test]
5484    fn ordinary_segment_allocation_ignores_reserved_ids_and_stops_at_boundary() {
5485        let reserved = super::SegmentId::new(super::RESERVED_SEGMENT_ID_NAMESPACE_START + 17);
5486        assert_eq!(
5487            super::next_ordinary_segment_id([super::SegmentId::new(7), reserved, super::SegmentId::new(9),]),
5488            Some(super::SegmentId::new(10))
5489        );
5490        assert_eq!(
5491            super::next_ordinary_segment_id([reserved]),
5492            Some(super::SegmentId::new(1))
5493        );
5494        assert_eq!(
5495            super::next_ordinary_segment_id([super::SegmentId::new(super::RESERVED_SEGMENT_ID_NAMESPACE_START - 1,)]),
5496            None
5497        );
5498    }
5499
5500    #[test]
5501    fn dense_numeric_and_binary_values_decode_to_identical_semantics() -> Result<(), Box<dyn std::error::Error>> {
5502        use base64::Engine as _;
5503
5504        for (element_type, scale, numeric, bytes) in [
5505            (
5506                super::DenseVectorElementType::F32,
5507                None,
5508                vec![1.5, -2.0],
5509                [1.5_f32.to_le_bytes(), (-2.0_f32).to_le_bytes()].concat(),
5510            ),
5511            (
5512                super::DenseVectorElementType::F16,
5513                None,
5514                vec![1.5, -2.0],
5515                [0x3e00_u16.to_le_bytes(), 0xc000_u16.to_le_bytes()].concat(),
5516            ),
5517            (
5518                super::DenseVectorElementType::I8,
5519                Some(super::VectorQuantizationScale::new(0.25)?),
5520                vec![6.0, -8.0],
5521                vec![6_u8, (-8_i8) as u8],
5522            ),
5523        ] {
5524            let definition = super::DenseVectorDefinition {
5525                dimensions: 2,
5526                element_type,
5527                metric: super::VectorMetric::DotProduct,
5528                quantization_scale: scale,
5529            };
5530            let numeric = super::decode_dense_numeric_value(definition, &numeric)?;
5531            let binary = super::decode_dense_binary_value(
5532                definition,
5533                &super::DenseVectorBinaryValue {
5534                    element_type,
5535                    byte_order: super::DenseVectorByteOrder::LittleEndian,
5536                    data_base64: super::BASE64_STANDARD.encode(bytes),
5537                    quantization_scale: scale,
5538                },
5539            )?;
5540            assert_eq!(numeric, binary);
5541        }
5542        Ok(())
5543    }
5544
5545    #[test]
5546    fn dense_value_refuses_narrowing_and_noncanonical_binary_controls() -> Result<(), Box<dyn std::error::Error>> {
5547        use base64::Engine as _;
5548
5549        let f16 = super::DenseVectorDefinition {
5550            dimensions: 1,
5551            element_type: super::DenseVectorElementType::F16,
5552            metric: super::VectorMetric::DotProduct,
5553            quantization_scale: None,
5554        };
5555        assert_eq!(super::decode_dense_numeric_value(f16, &[1.000_488_281_25])?, vec![1.0]);
5556        assert_eq!(
5557            super::decode_dense_numeric_value(f16, &[1.001_464_843_75])?,
5558            vec![1.001_953_1]
5559        );
5560        assert!(matches!(
5561            super::decode_dense_numeric_value(f16, &[70_000.0]),
5562            Err(super::DenseVectorValueError::F16Overflow(0))
5563        ));
5564
5565        let i8_definition = super::DenseVectorDefinition {
5566            dimensions: 1,
5567            element_type: super::DenseVectorElementType::I8,
5568            metric: super::VectorMetric::DotProduct,
5569            quantization_scale: Some(super::VectorQuantizationScale::new(0.5)?),
5570        };
5571        assert!(matches!(
5572            super::decode_dense_numeric_value(i8_definition, &[1.5]),
5573            Err(super::DenseVectorValueError::I8ElementOutOfRange(0))
5574        ));
5575        let encoded = super::BASE64_STANDARD.encode([1_u8]);
5576        let mut binary = super::DenseVectorBinaryValue {
5577            element_type: super::DenseVectorElementType::I8,
5578            byte_order: super::DenseVectorByteOrder::LittleEndian,
5579            data_base64: encoded,
5580            quantization_scale: Some(super::VectorQuantizationScale::new(0.25)?),
5581        };
5582        assert_eq!(
5583            super::decode_dense_binary_value(i8_definition, &binary),
5584            Err(super::DenseVectorValueError::QuantizationScaleMismatch)
5585        );
5586        binary.quantization_scale = i8_definition.quantization_scale;
5587        binary.data_base64.push('=');
5588        assert!(matches!(
5589            super::decode_dense_binary_value(i8_definition, &binary),
5590            Err(super::DenseVectorValueError::InvalidBase64Length { .. })
5591        ));
5592
5593        let wrong_order =
5594            br#"{"kind":"binary","element_type":"f32","byte_order":"big_endian","data_base64":"AAAAAA=="}"#;
5595        assert!(serde_json::from_slice::<super::DenseVectorValue>(wrong_order).is_err());
5596        let signed_zero = super::decode_dense_numeric_value(
5597            super::DenseVectorDefinition {
5598                dimensions: 2,
5599                element_type: super::DenseVectorElementType::F32,
5600                metric: super::VectorMetric::DotProduct,
5601                quantization_scale: None,
5602            },
5603            &[-0.0, 0.0],
5604        )?;
5605        assert!(signed_zero.iter().all(|value| value.to_bits() == 0));
5606        assert_eq!(
5607            super::decode_dense_numeric_value(
5608                super::DenseVectorDefinition {
5609                    dimensions: 2,
5610                    element_type: super::DenseVectorElementType::F32,
5611                    metric: super::VectorMetric::Cosine,
5612                    quantization_scale: None,
5613                },
5614                &[0.0, -0.0],
5615            ),
5616            Err(super::DenseVectorValueError::ZeroNorm)
5617        );
5618        Ok(())
5619    }
5620
5621    #[test]
5622    fn dense_value_refuses_length_scale_and_numeric_boundary_controls() -> Result<(), Box<dyn std::error::Error>> {
5623        use base64::Engine as _;
5624
5625        let f32_definition = super::DenseVectorDefinition {
5626            dimensions: 2,
5627            element_type: super::DenseVectorElementType::F32,
5628            metric: super::VectorMetric::DotProduct,
5629            quantization_scale: None,
5630        };
5631        for bytes in [vec![0_u8; 4], vec![0_u8; 12]] {
5632            let value = super::DenseVectorBinaryValue {
5633                element_type: super::DenseVectorElementType::F32,
5634                byte_order: super::DenseVectorByteOrder::LittleEndian,
5635                data_base64: super::BASE64_STANDARD.encode(bytes),
5636                quantization_scale: None,
5637            };
5638            assert!(matches!(
5639                super::decode_dense_binary_value(f32_definition, &value),
5640                Err(super::DenseVectorValueError::InvalidBase64Length { .. })
5641            ));
5642        }
5643        let wrong_type = super::DenseVectorBinaryValue {
5644            element_type: super::DenseVectorElementType::F16,
5645            byte_order: super::DenseVectorByteOrder::LittleEndian,
5646            data_base64: super::BASE64_STANDARD.encode([0_u8; 8]),
5647            quantization_scale: None,
5648        };
5649        assert_eq!(
5650            super::decode_dense_binary_value(f32_definition, &wrong_type),
5651            Err(super::DenseVectorValueError::ElementTypeMismatch)
5652        );
5653        assert!(matches!(
5654            super::decode_dense_numeric_value(f32_definition, &[f64::INFINITY, 0.0]),
5655            Err(super::DenseVectorValueError::NonFiniteElement(0))
5656        ));
5657
5658        let f16_definition = super::DenseVectorDefinition {
5659            dimensions: 1,
5660            element_type: super::DenseVectorElementType::F16,
5661            metric: super::VectorMetric::DotProduct,
5662            quantization_scale: None,
5663        };
5664        assert_eq!(
5665            super::decode_dense_numeric_value(f16_definition, &[2.0_f64.powi(-25)])?[0].to_bits(),
5666            0
5667        );
5668        assert_eq!(
5669            super::decode_dense_numeric_value(f16_definition, &[2.0_f64.powi(-24)])?[0],
5670            2.0_f32.powi(-24)
5671        );
5672        assert_eq!(
5673            super::decode_dense_numeric_value(f16_definition, &[3.0 * 2.0_f64.powi(-25)])?[0],
5674            2.0_f32.powi(-23)
5675        );
5676        let infinite_half = super::DenseVectorBinaryValue {
5677            element_type: super::DenseVectorElementType::F16,
5678            byte_order: super::DenseVectorByteOrder::LittleEndian,
5679            data_base64: super::BASE64_STANDARD.encode(0x7c00_u16.to_le_bytes()),
5680            quantization_scale: None,
5681        };
5682        assert!(matches!(
5683            super::decode_dense_binary_value(f16_definition, &infinite_half),
5684            Err(super::DenseVectorValueError::NonFiniteElement(0))
5685        ));
5686
5687        let i8_definition = super::DenseVectorDefinition {
5688            dimensions: 1,
5689            element_type: super::DenseVectorElementType::I8,
5690            metric: super::VectorMetric::DotProduct,
5691            quantization_scale: Some(super::VectorQuantizationScale::new(1.0)?),
5692        };
5693        for value in [-129.0, 128.0] {
5694            assert!(matches!(
5695                super::decode_dense_numeric_value(i8_definition, &[value]),
5696                Err(super::DenseVectorValueError::I8ElementOutOfRange(0))
5697            ));
5698        }
5699        for scale in [r#""0""#, r#""-1""#, r#""1.0""#] {
5700            assert!(serde_json::from_str::<super::VectorQuantizationScale>(scale).is_err());
5701        }
5702        Ok(())
5703    }
5704}