Skip to main content

lodestone_types/
derived_overload.rs

1//! P13.456A cache, adaptive-promotion, and overload semantic contracts.
2//!
3//! These types are deliberately not wired to a public handler. They freeze the
4//! current-format inputs and refusal rules that P13.4B, P13.5A, and P13.6A must
5//! implement without creating another durability or mapping authority.
6
7use std::error::Error;
8use std::fmt::{self, Display, Formatter};
9
10use serde::de::DeserializeOwned;
11use serde::{Deserialize, Serialize};
12
13use crate::{
14    CollectionId, CollectionIndexRef, CollectionSecurityGeneration, Epoch, IndexMode, IngestAuthorityV1, LogicalMillis,
15    ManifestGeneration, PolicyGeneration, RootCutId, SchemaGeneration, SegmentId, SeqNo, ShardId, ShardKey,
16};
17
18pub const DERIVED_OVERLOAD_FORMAT_VERSION: u16 = 3;
19pub const MAX_DERIVED_CONTRACT_BYTES: usize = 1_048_576;
20pub const MAX_CACHE_SHARDS: usize = 512;
21pub const MAX_CACHE_RESULT_WINDOW: u32 = 100_000;
22pub const MAX_CACHE_ENTRY_BYTES: u64 = 16 * 1_024 * 1_024;
23pub const MAX_CACHE_COLLECTION_BYTES: u64 = 512 * 1_024 * 1_024;
24pub const MAX_CACHE_COLLECTION_ENTRIES: u32 = 4_096;
25pub const MAX_PROMOTED_FIELDS: u16 = 64;
26pub const MAX_HEAT_RECORDS: u32 = 16_384;
27pub const MAX_DISTINCT_QUERIES_PER_HEAT_RECORD: u32 = 1_000_000;
28pub const MAX_PROMOTION_EXTRA_BYTES: u64 = 1_073_741_824;
29pub const MAX_PROMOTED_COLUMN_SIDECAR_BYTES: usize = 64 * 1_024 * 1_024;
30pub const MAX_PROMOTED_COLUMN_ROWS: u32 = 1_000_000;
31pub const PROMOTED_COLUMN_SIDECAR_NAME: &str = "field-promotion.v1.json";
32pub const MAX_OVERLOAD_BACKLOG_BYTES: u64 = 1_073_741_824;
33pub const MAX_OVERLOAD_BACKLOG_AGE_MILLIS: u64 = 300_000;
34pub const MAX_OVERLOAD_COLLECTION_SHARE_BYTES: u64 = 268_435_456;
35pub const MAX_OVERLOAD_RECOVERY_OPS_PER_PASS: u32 = 10_000;
36pub const MAX_PENDING_OVERLAY_DOCUMENTS: u64 = 1_000_000;
37
38fn digest_is_nonzero(value: &[u8; 32]) -> bool {
39    value.iter().any(|byte| *byte != 0)
40}
41
42fn require_nonzero_digest(value: &[u8; 32], name: &'static str) -> Result<(), DerivedContractError> {
43    if digest_is_nonzero(value) {
44        Ok(())
45    } else {
46        Err(DerivedContractError::Violation(name))
47    }
48}
49
50macro_rules! semantic_digest {
51    ($name:ident, $description:literal) => {
52        #[doc = $description]
53        #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
54        #[serde(transparent)]
55        pub struct $name([u8; 32]);
56
57        impl $name {
58            pub fn new(value: [u8; 32]) -> Result<Self, DerivedContractError> {
59                require_nonzero_digest(&value, concat!(stringify!($name), " cannot be zero"))?;
60                Ok(Self(value))
61            }
62
63            #[must_use]
64            pub const fn get(self) -> [u8; 32] {
65                self.0
66            }
67
68            fn validate(self) -> Result<(), DerivedContractError> {
69                require_nonzero_digest(&self.0, concat!(stringify!($name), " cannot be zero"))
70            }
71        }
72    };
73}
74
75macro_rules! semantic_generation {
76    ($name:ident, $description:literal) => {
77        #[doc = $description]
78        #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
79        #[serde(transparent)]
80        pub struct $name(u64);
81
82        impl $name {
83            pub const fn new(value: u64) -> Result<Self, DerivedContractError> {
84                if value == 0 {
85                    Err(DerivedContractError::Violation(concat!(
86                        stringify!($name),
87                        " cannot be zero"
88                    )))
89                } else {
90                    Ok(Self(value))
91                }
92            }
93
94            #[must_use]
95            pub const fn get(self) -> u64 {
96                self.0
97            }
98
99            fn validate(self) -> Result<(), DerivedContractError> {
100                Self::new(self.0).map(|_| ())
101            }
102        }
103    };
104}
105
106semantic_digest!(AuthorizedScopeDigestV1, "Digest of the exact authorized query scope.");
107semantic_digest!(NormalizedPlanDigestV1, "Digest of one canonical normalized query plan.");
108semantic_digest!(OutputSemanticsDigestV1, "Digest of result-affecting output semantics.");
109semantic_digest!(FeatureFlagsDigestV1, "Digest of result-affecting feature flags.");
110semantic_digest!(SnapshotDigestV1, "Digest of one exact PIT snapshot binding.");
111semantic_digest!(FieldPathDigestV1, "Opaque digest of one logical field path.");
112semantic_generation!(
113    CollectionResetGenerationV1,
114    "Collection-wide result-cache reset generation."
115);
116semantic_generation!(
117    DeletionOverlayGenerationV1,
118    "Current privacy/deletion overlay generation."
119);
120semantic_generation!(KeyAccessGenerationV1, "Current key-access authorization generation.");
121semantic_generation!(PromotionDecisionGenerationV1, "Physical promotion decision generation.");
122
123/// Computes the privacy-safe identity used by heat, decisions, and physical
124/// layout metadata. Raw field paths remain transient caller input.
125pub fn field_path_digest_v1(path: &str) -> Result<FieldPathDigestV1, DerivedContractError> {
126    if !crate::is_canonical_field_path(path) {
127        return Err(DerivedContractError::Violation(
128            "field path is not canonical and cannot be promoted",
129        ));
130    }
131    let mut hasher = blake3::Hasher::new();
132    hasher.update(b"lodestone/p13/field-path/v1\0");
133    hasher.update(path.as_bytes());
134    FieldPathDigestV1::new(*hasher.finalize().as_bytes())
135}
136
137/// One shard generation in an exact result-cache identity vector.
138#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
139#[serde(deny_unknown_fields)]
140pub struct CacheShardGenerationV1 {
141    pub shard_id: ShardId,
142    pub manifest_generation: ManifestGeneration,
143}
144
145/// The visibility class used to compute a result.
146#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
147#[serde(rename_all = "snake_case")]
148pub enum CacheFreshnessV1 {
149    Committed,
150    Realtime,
151}
152
153/// A realtime request may cache only its immutable committed sub-result.
154#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
155#[serde(rename_all = "snake_case")]
156pub enum CacheReuseScopeV1 {
157    CompleteCommittedResult,
158    CommittedSubresultOfRealtime,
159}
160
161/// Exact root binding used by the query. No unresolved clock is accepted.
162#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
163#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
164pub enum CacheSnapshotBindingV1 {
165    Current,
166    Pit { snapshot_digest: SnapshotDigestV1 },
167    RetainedHistory { root_cut_id: RootCutId },
168}
169
170impl CacheSnapshotBindingV1 {
171    fn validate(self) -> Result<(), DerivedContractError> {
172        match self {
173            Self::Current | Self::RetainedHistory { .. } => Ok(()),
174            Self::Pit { snapshot_digest } => snapshot_digest.validate(),
175        }
176    }
177}
178
179/// Complete semantic identity of one reusable result or committed sub-result.
180#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
181#[serde(deny_unknown_fields)]
182pub struct ResultCacheKeyV1 {
183    pub format_version: u16,
184    pub collection_index_ref: CollectionIndexRef,
185    pub collection_security_generation: CollectionSecurityGeneration,
186    pub authorized_scope_digest: AuthorizedScopeDigestV1,
187    pub policy_generation: PolicyGeneration,
188    pub collection_reset_generation: CollectionResetGenerationV1,
189    pub deletion_overlay_generation: DeletionOverlayGenerationV1,
190    pub key_access_generation: KeyAccessGenerationV1,
191    pub normalized_plan_digest: NormalizedPlanDigestV1,
192    pub output_semantics_digest: OutputSemanticsDigestV1,
193    pub expected_shard_count: u16,
194    pub shard_generations: Vec<CacheShardGenerationV1>,
195    pub mapping_generation: SchemaGeneration,
196    pub freshness: CacheFreshnessV1,
197    pub reuse_scope: CacheReuseScopeV1,
198    pub from: u32,
199    pub limit: u32,
200    pub feature_flags_digest: FeatureFlagsDigestV1,
201    pub snapshot: CacheSnapshotBindingV1,
202}
203
204impl ResultCacheKeyV1 {
205    pub fn validate(&self) -> Result<(), DerivedContractError> {
206        validate_format(self.format_version)?;
207        if self.collection_security_generation.get() == 0
208            || self.policy_generation.get() == 0
209            || self.mapping_generation.get() == 0
210        {
211            return Err(DerivedContractError::Violation(
212                "cache generations and authorization revisions must be nonzero",
213            ));
214        }
215        self.authorized_scope_digest.validate()?;
216        self.collection_reset_generation.validate()?;
217        self.deletion_overlay_generation.validate()?;
218        self.key_access_generation.validate()?;
219        self.normalized_plan_digest.validate()?;
220        self.output_semantics_digest.validate()?;
221        self.feature_flags_digest.validate()?;
222        if self.shard_generations.is_empty() || self.shard_generations.len() > MAX_CACHE_SHARDS {
223            return Err(DerivedContractError::Budget {
224                field: "cache shard generations",
225                observed: self.shard_generations.len() as u64,
226                maximum: MAX_CACHE_SHARDS as u64,
227            });
228        }
229        if usize::from(self.expected_shard_count) != self.shard_generations.len() {
230            return Err(DerivedContractError::Violation(
231                "cache shard vector does not contain the complete expected shard count",
232            ));
233        }
234        let mut previous = None;
235        for shard in &self.shard_generations {
236            if shard.manifest_generation.get() == 0 || previous.is_some_and(|prior| prior >= shard.shard_id) {
237                return Err(DerivedContractError::Violation(
238                    "cache shard generations must be nonzero, unique, and strictly ordered",
239                ));
240            }
241            previous = Some(shard.shard_id);
242        }
243        if self.limit == 0
244            || self
245                .from
246                .checked_add(self.limit)
247                .is_none_or(|window| window > MAX_CACHE_RESULT_WINDOW)
248        {
249            return Err(DerivedContractError::Violation(
250                "cache result window is outside the bounded contract",
251            ));
252        }
253        match (self.freshness, self.reuse_scope) {
254            (CacheFreshnessV1::Committed, CacheReuseScopeV1::CompleteCommittedResult)
255            | (CacheFreshnessV1::Realtime, CacheReuseScopeV1::CommittedSubresultOfRealtime) => {}
256            _ => {
257                return Err(DerivedContractError::Violation(
258                    "realtime requests may cache only an immutable committed sub-result",
259                ));
260            }
261        }
262        self.snapshot.validate()
263    }
264}
265
266/// Per-collection cache admission and eviction budget.
267#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
268#[serde(deny_unknown_fields)]
269pub struct ResultCacheBudgetV1 {
270    pub format_version: u16,
271    pub collection_id: CollectionId,
272    pub maximum_entry_bytes: u64,
273    pub maximum_collection_bytes: u64,
274    pub maximum_collection_entries: u32,
275}
276
277impl ResultCacheBudgetV1 {
278    pub fn validate(self) -> Result<(), DerivedContractError> {
279        validate_format(self.format_version)?;
280        if self.maximum_entry_bytes == 0
281            || self.maximum_entry_bytes > MAX_CACHE_ENTRY_BYTES
282            || self.maximum_collection_bytes < self.maximum_entry_bytes
283            || self.maximum_collection_bytes > MAX_CACHE_COLLECTION_BYTES
284            || self.maximum_collection_entries == 0
285            || self.maximum_collection_entries > MAX_CACHE_COLLECTION_ENTRIES
286        {
287            return Err(DerivedContractError::Violation(
288                "result-cache budget is outside the bounded contract",
289            ));
290        }
291        Ok(())
292    }
293}
294
295/// Numeric-only status; it cannot carry query text, document data, or cached bytes.
296#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
297#[serde(deny_unknown_fields)]
298pub struct ResultCacheMetricsV1 {
299    pub format_version: u16,
300    pub collection_id: CollectionId,
301    pub hits: u64,
302    pub misses: u64,
303    pub admissions: u64,
304    pub bypasses: u64,
305    pub evictions: u64,
306    pub collection_bytes: u64,
307    pub collection_entries: u32,
308}
309
310impl ResultCacheMetricsV1 {
311    pub fn validate(self, budget: ResultCacheBudgetV1) -> Result<(), DerivedContractError> {
312        validate_format(self.format_version)?;
313        budget.validate()?;
314        if self.collection_id != budget.collection_id
315            || self.collection_bytes > budget.maximum_collection_bytes
316            || self.collection_entries > budget.maximum_collection_entries
317        {
318            return Err(DerivedContractError::Violation(
319                "result-cache metrics exceed the recorded collection budget",
320            ));
321        }
322        Ok(())
323    }
324}
325
326/// Privacy classification applied before promotion is considered.
327#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
328#[serde(rename_all = "snake_case")]
329pub enum PromotionPrivacyV1 {
330    Ordinary,
331    Sensitive,
332    Restricted,
333}
334
335/// Closed physical type set for a promoted field representation.
336#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
337#[serde(rename_all = "snake_case")]
338pub enum PromotedFieldTypeV1 {
339    Keyword,
340    I64,
341    U64,
342    F64,
343    DateMillis,
344    Boolean,
345}
346
347/// Bounded, privacy-safe aggregate heat. Raw field names and query text are absent by construction.
348#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
349#[serde(deny_unknown_fields)]
350pub struct FieldHeatAggregateV1 {
351    pub format_version: u16,
352    pub collection_index_ref: CollectionIndexRef,
353    pub mapping_generation: SchemaGeneration,
354    pub field_path_digest: FieldPathDigestV1,
355    pub window_start_logical_millis: LogicalMillis,
356    pub window_end_logical_millis: LogicalMillis,
357    pub filter_uses: u64,
358    pub sort_uses: u64,
359    pub aggregation_uses: u64,
360    pub distinct_query_count: u32,
361    pub estimated_cardinality: u64,
362    pub estimated_extra_bytes: u64,
363    pub privacy: PromotionPrivacyV1,
364    pub field_type: PromotedFieldTypeV1,
365    pub observed_type_conflict: bool,
366    pub path_policy_eligible: bool,
367}
368
369impl FieldHeatAggregateV1 {
370    pub fn validate(&self) -> Result<(), DerivedContractError> {
371        validate_format(self.format_version)?;
372        if self.mapping_generation.get() == 0
373            || self.window_start_logical_millis.get() >= self.window_end_logical_millis.get()
374            || self.distinct_query_count == 0
375            || self.distinct_query_count > MAX_DISTINCT_QUERIES_PER_HEAT_RECORD
376            || self.estimated_extra_bytes > MAX_PROMOTION_EXTRA_BYTES
377        {
378            return Err(DerivedContractError::Violation(
379                "field heat aggregate is outside the bounded contract",
380            ));
381        }
382        self.field_path_digest.validate()?;
383        let total_uses = self
384            .filter_uses
385            .checked_add(self.sort_uses)
386            .and_then(|uses| uses.checked_add(self.aggregation_uses))
387            .ok_or(DerivedContractError::ArithmeticOverflow)?;
388        if total_uses == 0 || u64::from(self.distinct_query_count) > total_uses {
389            return Err(DerivedContractError::Violation(
390                "field heat requires enough physical operations for its distinct-query count",
391            ));
392        }
393        Ok(())
394    }
395}
396
397/// Policy limits for physical field promotion. Logical mapping semantics remain authoritative.
398#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
399#[serde(deny_unknown_fields)]
400pub struct FieldPromotionPolicyV1 {
401    pub format_version: u16,
402    pub collection_index_ref: CollectionIndexRef,
403    pub mapping_generation: SchemaGeneration,
404    pub policy_generation: PolicyGeneration,
405    pub enabled: bool,
406    pub minimum_query_count: u32,
407    pub maximum_promoted_fields: u16,
408    pub maximum_cardinality: u64,
409    pub maximum_extra_bytes: u64,
410    pub allow_sensitive: bool,
411}
412
413impl FieldPromotionPolicyV1 {
414    pub fn validate(self) -> Result<(), DerivedContractError> {
415        validate_format(self.format_version)?;
416        if self.mapping_generation.get() == 0
417            || self.policy_generation.get() == 0
418            || self.minimum_query_count == 0
419            || self.minimum_query_count > MAX_DISTINCT_QUERIES_PER_HEAT_RECORD
420            || self.maximum_promoted_fields == 0
421            || self.maximum_promoted_fields > MAX_PROMOTED_FIELDS
422            || self.maximum_cardinality == 0
423            || self.maximum_extra_bytes == 0
424            || self.maximum_extra_bytes > MAX_PROMOTION_EXTRA_BYTES
425        {
426            return Err(DerivedContractError::Violation(
427                "field-promotion policy is outside the bounded contract",
428            ));
429        }
430        Ok(())
431    }
432
433    /// Returns a physical-only decision; denial never changes logical mapping behavior.
434    pub fn admits(
435        self,
436        heat: &FieldHeatAggregateV1,
437        currently_promoted_fields: u16,
438        currently_promoted_extra_bytes: u64,
439    ) -> Result<bool, DerivedContractError> {
440        self.validate()?;
441        heat.validate()?;
442        if heat.collection_index_ref != self.collection_index_ref || heat.mapping_generation != self.mapping_generation
443        {
444            return Err(DerivedContractError::Violation(
445                "field heat does not belong to the promotion policy scope and mapping generation",
446            ));
447        }
448        if currently_promoted_fields > self.maximum_promoted_fields
449            || currently_promoted_extra_bytes > self.maximum_extra_bytes
450        {
451            return Err(DerivedContractError::Violation(
452                "current promoted layout exceeds the governing policy",
453            ));
454        }
455        let projected_extra_bytes = currently_promoted_extra_bytes
456            .checked_add(heat.estimated_extra_bytes)
457            .ok_or(DerivedContractError::ArithmeticOverflow)?;
458        Ok(self.enabled
459            && heat.distinct_query_count >= self.minimum_query_count
460            && heat.estimated_cardinality <= self.maximum_cardinality
461            && projected_extra_bytes <= self.maximum_extra_bytes
462            && currently_promoted_fields < self.maximum_promoted_fields
463            && !heat.observed_type_conflict
464            && heat.path_policy_eligible
465            && heat.privacy != PromotionPrivacyV1::Restricted
466            && (heat.privacy != PromotionPrivacyV1::Sensitive || self.allow_sensitive))
467    }
468}
469
470/// Per-segment physical layout. Readers must branch per segment and preserve logical equivalence.
471#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
472#[serde(deny_unknown_fields)]
473pub struct PromotedFieldLayoutV1 {
474    pub format_version: u16,
475    pub collection_index_ref: CollectionIndexRef,
476    pub segment_id: SegmentId,
477    pub field_path_digest: FieldPathDigestV1,
478    pub mapping_generation: SchemaGeneration,
479    pub decision_generation: PromotionDecisionGenerationV1,
480    pub field_type: PromotedFieldTypeV1,
481}
482
483impl PromotedFieldLayoutV1 {
484    pub fn validate(self) -> Result<(), DerivedContractError> {
485        validate_format(self.format_version)?;
486        self.field_path_digest.validate()?;
487        self.decision_generation.validate()?;
488        if self.segment_id.get() == 0 || self.mapping_generation.get() == 0 {
489            return Err(DerivedContractError::Violation(
490                "promoted layout generations must be nonzero",
491            ));
492        }
493        Ok(())
494    }
495}
496
497/// Numeric/digest-only promotion status; it contains no raw field path or query text.
498#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
499#[serde(deny_unknown_fields)]
500pub struct FieldPromotionStatusV1 {
501    pub format_version: u16,
502    pub collection_index_ref: CollectionIndexRef,
503    pub mapping_generation: SchemaGeneration,
504    pub policy_generation: PolicyGeneration,
505    pub last_decision_generation: Option<PromotionDecisionGenerationV1>,
506    pub heat_records: u32,
507    pub promoted_fields: u16,
508    pub extra_bytes: u64,
509    pub enabled: bool,
510}
511
512impl FieldPromotionStatusV1 {
513    pub fn validate(self, policy: FieldPromotionPolicyV1) -> Result<(), DerivedContractError> {
514        validate_format(self.format_version)?;
515        policy.validate()?;
516        if self.collection_index_ref != policy.collection_index_ref
517            || self.mapping_generation != policy.mapping_generation
518            || self.policy_generation != policy.policy_generation
519            || self.enabled != policy.enabled
520            || self.heat_records > MAX_HEAT_RECORDS
521            || self.promoted_fields > policy.maximum_promoted_fields
522            || self.extra_bytes > policy.maximum_extra_bytes
523            || (self.promoted_fields > 0 && self.last_decision_generation.is_none())
524            || (self.promoted_fields == 0 && self.extra_bytes != 0)
525        {
526            return Err(DerivedContractError::Violation(
527                "field-promotion status is inconsistent with the governing policy",
528            ));
529        }
530        if let Some(generation) = self.last_decision_generation {
531            generation.validate()?;
532        }
533        Ok(())
534    }
535}
536
537/// Canonical scalar stored in a derived promoted column. Floating-point
538/// values use normalized IEEE bits so equality and ordering remain explicit.
539#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
540#[serde(tag = "kind", content = "value", rename_all = "snake_case", deny_unknown_fields)]
541pub enum PromotedFieldValueV1 {
542    Keyword(String),
543    I64(i64),
544    U64(u64),
545    F64Bits(u64),
546    DateMillis(u64),
547    Boolean(bool),
548}
549
550impl PromotedFieldValueV1 {
551    pub fn validate_for_type(&self, field_type: PromotedFieldTypeV1) -> Result<(), DerivedContractError> {
552        let matches_type = matches!(
553            (self, field_type),
554            (Self::Keyword(_), PromotedFieldTypeV1::Keyword)
555                | (Self::I64(_), PromotedFieldTypeV1::I64)
556                | (Self::U64(_), PromotedFieldTypeV1::U64)
557                | (Self::F64Bits(_), PromotedFieldTypeV1::F64)
558                | (Self::DateMillis(_), PromotedFieldTypeV1::DateMillis)
559                | (Self::Boolean(_), PromotedFieldTypeV1::Boolean)
560        );
561        if !matches_type {
562            return Err(DerivedContractError::Violation(
563                "promoted column value does not match its closed physical type",
564            ));
565        }
566        match self {
567            Self::Keyword(value) if value.len() > crate::MAX_MAPPING_FIELD_NAME_BYTES.saturating_mul(64) => Err(
568                DerivedContractError::Violation("promoted keyword value exceeds the bounded scalar representation"),
569            ),
570            Self::F64Bits(bits) if !f64::from_bits(*bits).is_finite() => Err(DerivedContractError::Violation(
571                "promoted floating-point value must be finite",
572            )),
573            Self::F64Bits(bits) if f64::from_bits(*bits) == 0.0 && *bits != 0.0_f64.to_bits() => Err(
574                DerivedContractError::Violation("promoted floating-point zero must use canonical positive-zero bits"),
575            ),
576            _ => Ok(()),
577        }
578    }
579}
580
581/// One present scalar at an exact physical document ordinal. Missing/null
582/// values are omitted rather than assigned a competing null interpretation.
583#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
584#[serde(deny_unknown_fields)]
585pub struct PromotedFieldRowV1 {
586    pub document_ordinal: u32,
587    pub value: PromotedFieldValueV1,
588}
589
590/// One immutable physical column for a field digest in a segment.
591#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
592#[serde(deny_unknown_fields)]
593pub struct PromotedFieldColumnV1 {
594    pub layout: PromotedFieldLayoutV1,
595    pub rows: Vec<PromotedFieldRowV1>,
596}
597
598impl PromotedFieldColumnV1 {
599    pub fn validate(&self, document_count: u32) -> Result<(), DerivedContractError> {
600        self.layout.validate()?;
601        if self.rows.len() > document_count as usize || self.rows.len() > MAX_PROMOTED_COLUMN_ROWS as usize {
602            return Err(DerivedContractError::Budget {
603                field: "promoted column rows",
604                observed: self.rows.len() as u64,
605                maximum: u64::from(document_count.min(MAX_PROMOTED_COLUMN_ROWS)),
606            });
607        }
608        let mut previous = None;
609        for row in &self.rows {
610            if row.document_ordinal >= document_count || previous.is_some_and(|prior| prior >= row.document_ordinal) {
611                return Err(DerivedContractError::Violation(
612                    "promoted column rows must be unique, ordered, and inside the segment",
613                ));
614            }
615            row.value.validate_for_type(self.layout.field_type)?;
616            previous = Some(row.document_ordinal);
617        }
618        Ok(())
619    }
620}
621
622/// Current immutable promoted-column sidecar. It is a child of the ordinary
623/// segment/root graph and has no independent publication authority.
624#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
625#[serde(deny_unknown_fields)]
626pub struct PromotedColumnSidecarV1 {
627    pub format_version: u16,
628    pub collection_index_ref: CollectionIndexRef,
629    pub segment_id: SegmentId,
630    pub mapping_generation: SchemaGeneration,
631    pub document_count: u32,
632    pub columns: Vec<PromotedFieldColumnV1>,
633}
634
635impl PromotedColumnSidecarV1 {
636    pub fn validate(&self) -> Result<(), DerivedContractError> {
637        validate_format(self.format_version)?;
638        if self.segment_id.get() == 0
639            || self.mapping_generation.get() == 0
640            || self.document_count > MAX_PROMOTED_COLUMN_ROWS
641            || self.columns.is_empty()
642            || self.columns.len() > usize::from(MAX_PROMOTED_FIELDS)
643        {
644            return Err(DerivedContractError::Violation(
645                "promoted column sidecar is outside the bounded segment contract",
646            ));
647        }
648        let mut previous_digest = None;
649        let mut decision_generations = std::collections::BTreeSet::new();
650        for column in &self.columns {
651            let layout = column.layout;
652            if layout.collection_index_ref != self.collection_index_ref
653                || layout.segment_id != self.segment_id
654                || layout.mapping_generation != self.mapping_generation
655                || previous_digest.is_some_and(|previous| previous >= layout.field_path_digest.get())
656            {
657                return Err(DerivedContractError::Violation(
658                    "promoted sidecar columns must match the segment and be strictly digest ordered",
659                ));
660            }
661            if !decision_generations.insert(layout.decision_generation.get()) {
662                return Err(DerivedContractError::Violation(
663                    "promoted sidecar decision generations must be unique",
664                ));
665            }
666            column.validate(self.document_count)?;
667            previous_digest = Some(layout.field_path_digest.get());
668        }
669        Ok(())
670    }
671
672    #[must_use]
673    pub fn column(&self, digest: FieldPathDigestV1) -> Option<&PromotedFieldColumnV1> {
674        self.columns
675            .binary_search_by_key(&digest.get(), |column| column.layout.field_path_digest.get())
676            .ok()
677            .and_then(|index| self.columns.get(index))
678    }
679}
680
681#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
682#[serde(rename_all = "snake_case")]
683pub enum OverloadModeV1 {
684    Shed,
685    Buffer,
686}
687
688/// Bounded overload behavior. A buffer is a projection of the existing registered WAL.
689#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
690#[serde(deny_unknown_fields)]
691pub struct OverloadPolicyV1 {
692    pub format_version: u16,
693    pub collection_index_ref: CollectionIndexRef,
694    pub policy_generation: PolicyGeneration,
695    pub mode: OverloadModeV1,
696    pub maximum_backlog_bytes: u64,
697    pub maximum_backlog_age_millis: u64,
698    pub maximum_collection_share_bytes: u64,
699    pub maximum_recovery_ops_per_pass: u32,
700    pub maximum_pending_overlay_documents: u64,
701}
702
703impl OverloadPolicyV1 {
704    pub fn validate(self) -> Result<(), DerivedContractError> {
705        validate_format(self.format_version)?;
706        if self.policy_generation.get() == 0
707            || self.maximum_backlog_bytes == 0
708            || self.maximum_backlog_bytes > MAX_OVERLOAD_BACKLOG_BYTES
709            || self.maximum_backlog_age_millis == 0
710            || self.maximum_backlog_age_millis > MAX_OVERLOAD_BACKLOG_AGE_MILLIS
711            || self.maximum_collection_share_bytes == 0
712            || self.maximum_collection_share_bytes > self.maximum_backlog_bytes
713            || self.maximum_collection_share_bytes > MAX_OVERLOAD_COLLECTION_SHARE_BYTES
714            || self.maximum_recovery_ops_per_pass == 0
715            || self.maximum_recovery_ops_per_pass > MAX_OVERLOAD_RECOVERY_OPS_PER_PASS
716            || self.maximum_pending_overlay_documents == 0
717            || self.maximum_pending_overlay_documents > MAX_PENDING_OVERLAY_DOCUMENTS
718        {
719            return Err(DerivedContractError::Violation(
720                "overload policy is outside the bounded contract",
721            ));
722        }
723        Ok(())
724    }
725}
726
727#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
728#[serde(rename_all = "snake_case")]
729pub enum OverloadDispositionV1 {
730    Shed,
731    BufferRegisteredWal,
732    PauseReplayableSource,
733    RefuseStateMode,
734    RefuseMissingMutableOverlay,
735}
736
737/// Selects policy without changing the existing acknowledgment authority.
738pub fn overload_disposition_v1(
739    policy: OverloadPolicyV1,
740    index_mode: IndexMode,
741    ingest_authority: &IngestAuthorityV1,
742    exact_mutable_overlay_available: bool,
743) -> Result<OverloadDispositionV1, DerivedContractError> {
744    policy.validate()?;
745    // INVARIANT: state plus replayable-source is an intentional overload
746    // refusal, but the nested authority still has to be valid current format.
747    // Validate that nested shape in a source-compatible mode before selecting
748    // the state refusal below.
749    let validation_mode =
750        if index_mode == IndexMode::State && matches!(ingest_authority, IngestAuthorityV1::ReplayableSource { .. }) {
751            IndexMode::AppendLog
752        } else {
753            index_mode
754        };
755    ingest_authority
756        .validate_for_mode(validation_mode)
757        .map_err(DerivedContractError::source)?;
758    if policy.mode == OverloadModeV1::Shed {
759        return Ok(OverloadDispositionV1::Shed);
760    }
761    if index_mode == IndexMode::State {
762        return Ok(OverloadDispositionV1::RefuseStateMode);
763    }
764    if matches!(ingest_authority, IngestAuthorityV1::ReplayableSource { .. }) {
765        return Ok(OverloadDispositionV1::PauseReplayableSource);
766    }
767    match index_mode {
768        IndexMode::AppendLog => Ok(OverloadDispositionV1::BufferRegisteredWal),
769        IndexMode::Mutable if exact_mutable_overlay_available => Ok(OverloadDispositionV1::BufferRegisteredWal),
770        IndexMode::Mutable => Ok(OverloadDispositionV1::RefuseMissingMutableOverlay),
771        IndexMode::State => Ok(OverloadDispositionV1::RefuseStateMode),
772    }
773}
774
775/// Exact collector-level pending-mutation coverage derived from registered WAL records.
776#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
777#[serde(deny_unknown_fields)]
778pub struct PendingMutationOverlayV1 {
779    pub format_version: u16,
780    pub shard_key: ShardKey,
781    pub writer_epoch: Epoch,
782    pub registered_through: SeqNo,
783    pub indexed_through: SeqNo,
784    pub get_overlay_through: SeqNo,
785    pub conditional_write_through: SeqNo,
786    pub collector_visibility_through: SeqNo,
787    pub subscription_through: SeqNo,
788    pub pending_documents: u64,
789    pub pending_tombstones: u64,
790    pub pending_bytes: u64,
791    pub oldest_age_millis: u64,
792}
793
794impl PendingMutationOverlayV1 {
795    pub fn validate(self) -> Result<(), DerivedContractError> {
796        validate_format(self.format_version)?;
797        let durable = self.registered_through.get();
798        let has_no_pending_work = self.pending_documents == 0
799            && self.pending_tombstones == 0
800            && self.pending_bytes == 0
801            && self.oldest_age_millis == 0;
802        if self.writer_epoch.get() == 0
803            || self.indexed_through.get() > durable
804            || self.get_overlay_through.get() != durable
805            || self.conditional_write_through.get() != durable
806            || self.collector_visibility_through.get() != durable
807            || self.subscription_through.get() != durable
808            || self.pending_documents > MAX_PENDING_OVERLAY_DOCUMENTS
809            || self.pending_tombstones > self.pending_documents
810            || self.pending_bytes > MAX_OVERLOAD_BACKLOG_BYTES
811            || self.oldest_age_millis > MAX_OVERLOAD_BACKLOG_AGE_MILLIS
812            || (self.indexed_through == self.registered_through && !has_no_pending_work)
813        {
814            return Err(DerivedContractError::Violation(
815                "pending mutation overlay does not exactly cover the registered WAL cut",
816            ));
817        }
818        Ok(())
819    }
820
821    pub fn lag_operations(self) -> Result<u64, DerivedContractError> {
822        self.validate()?;
823        Ok(self.registered_through.get() - self.indexed_through.get())
824    }
825}
826
827#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
828#[serde(rename_all = "snake_case")]
829pub enum OverloadShedReasonV1 {
830    BacklogBytes,
831    BacklogAge,
832    CollectionShare,
833    RecoveryWork,
834    PendingOverlayDocuments,
835}
836
837/// Deterministic cap evaluation order used by every writer replica.
838pub fn overload_shed_reason_v1(
839    policy: OverloadPolicyV1,
840    backlog_bytes: u64,
841    backlog_age_millis: u64,
842    collection_share_bytes: u64,
843    recovery_ops_this_pass: u32,
844    pending_overlay_documents: u64,
845) -> Result<Option<OverloadShedReasonV1>, DerivedContractError> {
846    policy.validate()?;
847    Ok(if backlog_bytes > policy.maximum_backlog_bytes {
848        Some(OverloadShedReasonV1::BacklogBytes)
849    } else if backlog_age_millis > policy.maximum_backlog_age_millis {
850        Some(OverloadShedReasonV1::BacklogAge)
851    } else if collection_share_bytes > policy.maximum_collection_share_bytes {
852        Some(OverloadShedReasonV1::CollectionShare)
853    } else if recovery_ops_this_pass > policy.maximum_recovery_ops_per_pass {
854        Some(OverloadShedReasonV1::RecoveryWork)
855    } else if pending_overlay_documents > policy.maximum_pending_overlay_documents {
856        Some(OverloadShedReasonV1::PendingOverlayDocuments)
857    } else {
858        None
859    })
860}
861
862/// Honest numeric overload status for one exact writer incarnation.
863#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
864#[serde(deny_unknown_fields)]
865pub struct OverloadStatusV1 {
866    pub format_version: u16,
867    pub shard_key: ShardKey,
868    pub writer_epoch: Epoch,
869    pub configured_mode: OverloadModeV1,
870    pub effective_disposition: OverloadDispositionV1,
871    pub registered_through: SeqNo,
872    pub indexed_through: SeqNo,
873    pub lag_operations: u64,
874    pub backlog_bytes: u64,
875    pub oldest_age_millis: u64,
876    pub collection_share_bytes: u64,
877    pub recovery_ops_this_pass: u32,
878    pub pending_overlay_documents: u64,
879    pub shed_reason: Option<OverloadShedReasonV1>,
880}
881
882impl OverloadStatusV1 {
883    pub fn validate(
884        self,
885        policy: OverloadPolicyV1,
886        index_mode: IndexMode,
887        ingest_authority: &IngestAuthorityV1,
888        exact_mutable_overlay_available: bool,
889    ) -> Result<(), DerivedContractError> {
890        validate_format(self.format_version)?;
891        policy.validate()?;
892        if self.writer_epoch.get() == 0
893            || self.shard_key.collection_index_ref() != policy.collection_index_ref
894            || self.configured_mode != policy.mode
895            || self.indexed_through.get() > self.registered_through.get()
896            || self.lag_operations != self.registered_through.get() - self.indexed_through.get()
897            || self.collection_share_bytes > self.backlog_bytes
898        {
899            return Err(DerivedContractError::Violation(
900                "overload status watermarks or policy binding are inconsistent",
901            ));
902        }
903        let expected_reason = overload_shed_reason_v1(
904            policy,
905            self.backlog_bytes,
906            self.oldest_age_millis,
907            self.collection_share_bytes,
908            self.recovery_ops_this_pass,
909            self.pending_overlay_documents,
910        )?;
911        let configured_disposition =
912            overload_disposition_v1(policy, index_mode, ingest_authority, exact_mutable_overlay_available)?;
913        let expected_disposition = if expected_reason.is_some() {
914            OverloadDispositionV1::Shed
915        } else {
916            configured_disposition
917        };
918        if self.shed_reason != expected_reason || self.effective_disposition != expected_disposition {
919            return Err(DerivedContractError::Violation(
920                "overload status does not report the deterministic cap disposition",
921            ));
922        }
923        Ok(())
924    }
925}
926
927pub fn encode_result_cache_key_v1(value: &ResultCacheKeyV1) -> Result<Vec<u8>, DerivedContractError> {
928    value.validate()?;
929    encode_bounded(value)
930}
931
932pub fn decode_result_cache_key_v1(bytes: &[u8]) -> Result<ResultCacheKeyV1, DerivedContractError> {
933    decode_current(bytes, ResultCacheKeyV1::validate)
934}
935
936pub fn encode_result_cache_budget_v1(value: &ResultCacheBudgetV1) -> Result<Vec<u8>, DerivedContractError> {
937    value.validate()?;
938    encode_bounded(value)
939}
940
941pub fn decode_result_cache_budget_v1(bytes: &[u8]) -> Result<ResultCacheBudgetV1, DerivedContractError> {
942    decode_current(bytes, |value: &ResultCacheBudgetV1| (*value).validate())
943}
944
945pub fn encode_result_cache_metrics_v1(
946    value: &ResultCacheMetricsV1,
947    budget: ResultCacheBudgetV1,
948) -> Result<Vec<u8>, DerivedContractError> {
949    value.validate(budget)?;
950    encode_bounded(value)
951}
952
953pub fn decode_result_cache_metrics_v1(
954    bytes: &[u8],
955    budget: ResultCacheBudgetV1,
956) -> Result<ResultCacheMetricsV1, DerivedContractError> {
957    decode_current(bytes, |value: &ResultCacheMetricsV1| (*value).validate(budget))
958}
959
960pub fn encode_field_heat_v1(value: &FieldHeatAggregateV1) -> Result<Vec<u8>, DerivedContractError> {
961    value.validate()?;
962    encode_bounded(value)
963}
964
965pub fn decode_field_heat_v1(bytes: &[u8]) -> Result<FieldHeatAggregateV1, DerivedContractError> {
966    decode_current(bytes, FieldHeatAggregateV1::validate)
967}
968
969pub fn encode_field_promotion_policy_v1(value: &FieldPromotionPolicyV1) -> Result<Vec<u8>, DerivedContractError> {
970    value.validate()?;
971    encode_bounded(value)
972}
973
974pub fn decode_field_promotion_policy_v1(bytes: &[u8]) -> Result<FieldPromotionPolicyV1, DerivedContractError> {
975    decode_current(bytes, |value: &FieldPromotionPolicyV1| (*value).validate())
976}
977
978pub fn encode_promoted_field_layout_v1(value: &PromotedFieldLayoutV1) -> Result<Vec<u8>, DerivedContractError> {
979    value.validate()?;
980    encode_bounded(value)
981}
982
983pub fn decode_promoted_field_layout_v1(bytes: &[u8]) -> Result<PromotedFieldLayoutV1, DerivedContractError> {
984    decode_current(bytes, |value: &PromotedFieldLayoutV1| (*value).validate())
985}
986
987pub fn encode_field_promotion_status_v1(
988    value: &FieldPromotionStatusV1,
989    policy: FieldPromotionPolicyV1,
990) -> Result<Vec<u8>, DerivedContractError> {
991    value.validate(policy)?;
992    encode_bounded(value)
993}
994
995pub fn decode_field_promotion_status_v1(
996    bytes: &[u8],
997    policy: FieldPromotionPolicyV1,
998) -> Result<FieldPromotionStatusV1, DerivedContractError> {
999    decode_current(bytes, |value: &FieldPromotionStatusV1| (*value).validate(policy))
1000}
1001
1002pub fn encode_promoted_column_sidecar_v1(value: &PromotedColumnSidecarV1) -> Result<Vec<u8>, DerivedContractError> {
1003    value.validate()?;
1004    let bytes = serde_json::to_vec(value).map_err(DerivedContractError::serde)?;
1005    if bytes.len() > MAX_PROMOTED_COLUMN_SIDECAR_BYTES {
1006        return Err(DerivedContractError::Budget {
1007            field: "promoted column sidecar bytes",
1008            observed: bytes.len() as u64,
1009            maximum: MAX_PROMOTED_COLUMN_SIDECAR_BYTES as u64,
1010        });
1011    }
1012    Ok(bytes)
1013}
1014
1015pub fn decode_promoted_column_sidecar_v1(bytes: &[u8]) -> Result<PromotedColumnSidecarV1, DerivedContractError> {
1016    if bytes.len() > MAX_PROMOTED_COLUMN_SIDECAR_BYTES {
1017        return Err(DerivedContractError::Budget {
1018            field: "promoted column sidecar bytes",
1019            observed: bytes.len() as u64,
1020            maximum: MAX_PROMOTED_COLUMN_SIDECAR_BYTES as u64,
1021        });
1022    }
1023    #[derive(Deserialize)]
1024    struct FormatProbe {
1025        format_version: Option<u16>,
1026    }
1027    let probe: FormatProbe = serde_json::from_slice(bytes).map_err(DerivedContractError::serde)?;
1028    validate_format(probe.format_version.ok_or(DerivedContractError::MissingFormatVersion)?)?;
1029    let decoded: PromotedColumnSidecarV1 = serde_json::from_slice(bytes).map_err(DerivedContractError::serde)?;
1030    decoded.validate()?;
1031    if serde_json::to_vec(&decoded).map_err(DerivedContractError::serde)? != bytes {
1032        return Err(DerivedContractError::NonCanonical);
1033    }
1034    Ok(decoded)
1035}
1036
1037pub fn encode_overload_policy_v1(value: &OverloadPolicyV1) -> Result<Vec<u8>, DerivedContractError> {
1038    value.validate()?;
1039    encode_bounded(value)
1040}
1041
1042pub fn decode_overload_policy_v1(bytes: &[u8]) -> Result<OverloadPolicyV1, DerivedContractError> {
1043    decode_current(bytes, |value: &OverloadPolicyV1| (*value).validate())
1044}
1045
1046pub fn encode_overload_status_v1(
1047    value: &OverloadStatusV1,
1048    policy: OverloadPolicyV1,
1049    index_mode: IndexMode,
1050    ingest_authority: &IngestAuthorityV1,
1051    exact_mutable_overlay_available: bool,
1052) -> Result<Vec<u8>, DerivedContractError> {
1053    value.validate(policy, index_mode, ingest_authority, exact_mutable_overlay_available)?;
1054    encode_bounded(value)
1055}
1056
1057pub fn decode_overload_status_v1(
1058    bytes: &[u8],
1059    policy: OverloadPolicyV1,
1060    index_mode: IndexMode,
1061    ingest_authority: &IngestAuthorityV1,
1062    exact_mutable_overlay_available: bool,
1063) -> Result<OverloadStatusV1, DerivedContractError> {
1064    decode_current(bytes, |value: &OverloadStatusV1| {
1065        (*value).validate(policy, index_mode, ingest_authority, exact_mutable_overlay_available)
1066    })
1067}
1068
1069pub fn encode_pending_overlay_v1(value: &PendingMutationOverlayV1) -> Result<Vec<u8>, DerivedContractError> {
1070    value.validate()?;
1071    encode_bounded(value)
1072}
1073
1074pub fn decode_pending_overlay_v1(bytes: &[u8]) -> Result<PendingMutationOverlayV1, DerivedContractError> {
1075    decode_current(bytes, |value: &PendingMutationOverlayV1| (*value).validate())
1076}
1077
1078fn encode_bounded<T: Serialize>(value: &T) -> Result<Vec<u8>, DerivedContractError> {
1079    let bytes = serde_json::to_vec(value).map_err(DerivedContractError::serde)?;
1080    if bytes.len() > MAX_DERIVED_CONTRACT_BYTES {
1081        return Err(DerivedContractError::Budget {
1082            field: "derived contract bytes",
1083            observed: bytes.len() as u64,
1084            maximum: MAX_DERIVED_CONTRACT_BYTES as u64,
1085        });
1086    }
1087    Ok(bytes)
1088}
1089
1090fn decode_current<T>(
1091    bytes: &[u8],
1092    validate: impl FnOnce(&T) -> Result<(), DerivedContractError>,
1093) -> Result<T, DerivedContractError>
1094where
1095    T: DeserializeOwned + Serialize,
1096{
1097    #[derive(Deserialize)]
1098    struct FormatProbe {
1099        format_version: Option<u16>,
1100    }
1101
1102    if bytes.len() > MAX_DERIVED_CONTRACT_BYTES {
1103        return Err(DerivedContractError::Budget {
1104            field: "derived contract bytes",
1105            observed: bytes.len() as u64,
1106            maximum: MAX_DERIVED_CONTRACT_BYTES as u64,
1107        });
1108    }
1109    let probe: FormatProbe = serde_json::from_slice(bytes).map_err(DerivedContractError::serde)?;
1110    let version = probe.format_version.ok_or(DerivedContractError::MissingFormatVersion)?;
1111    validate_format(version)?;
1112    let decoded: T = serde_json::from_slice(bytes).map_err(DerivedContractError::serde)?;
1113    validate(&decoded)?;
1114    if serde_json::to_vec(&decoded).map_err(DerivedContractError::serde)? != bytes {
1115        return Err(DerivedContractError::NonCanonical);
1116    }
1117    Ok(decoded)
1118}
1119
1120fn validate_format(version: u16) -> Result<(), DerivedContractError> {
1121    if version == DERIVED_OVERLOAD_FORMAT_VERSION {
1122        Ok(())
1123    } else {
1124        Err(DerivedContractError::UnsupportedFormat(version))
1125    }
1126}
1127
1128#[derive(Clone, Debug, Eq, PartialEq)]
1129pub enum DerivedContractError {
1130    UnsupportedFormat(u16),
1131    MissingFormatVersion,
1132    NonCanonical,
1133    Budget {
1134        field: &'static str,
1135        observed: u64,
1136        maximum: u64,
1137    },
1138    Violation(&'static str),
1139    ArithmeticOverflow,
1140    Source(String),
1141    Serde(String),
1142}
1143
1144impl DerivedContractError {
1145    fn serde(error: serde_json::Error) -> Self {
1146        Self::Serde(error.to_string())
1147    }
1148
1149    fn source(error: impl Display) -> Self {
1150        Self::Source(error.to_string())
1151    }
1152}
1153
1154impl Display for DerivedContractError {
1155    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
1156        match self {
1157            Self::UnsupportedFormat(version) => write!(formatter, "unsupported derived/overload format {version}"),
1158            Self::MissingFormatVersion => formatter.write_str("derived/overload format version is missing"),
1159            Self::NonCanonical => formatter.write_str("derived/overload bytes are not canonical current format"),
1160            Self::Budget {
1161                field,
1162                observed,
1163                maximum,
1164            } => write!(formatter, "{field} {observed} exceeds {maximum}"),
1165            Self::Violation(message) => formatter.write_str(message),
1166            Self::ArithmeticOverflow => formatter.write_str("derived/overload arithmetic overflowed"),
1167            Self::Source(message) => write!(formatter, "derived/overload source authority refused input: {message}"),
1168            Self::Serde(message) => write!(formatter, "derived/overload codec refused input: {message}"),
1169        }
1170    }
1171}
1172
1173impl Error for DerivedContractError {}
1174
1175#[cfg(test)]
1176mod tests {
1177    use super::*;
1178    use crate::{CollectionId, IndexId};
1179
1180    fn scope() -> Result<CollectionIndexRef, Box<dyn Error>> {
1181        Ok(CollectionIndexRef::new(CollectionId::new(1)?, IndexId::new(1)?))
1182    }
1183
1184    fn cache_key() -> Result<ResultCacheKeyV1, Box<dyn Error>> {
1185        Ok(ResultCacheKeyV1 {
1186            format_version: DERIVED_OVERLOAD_FORMAT_VERSION,
1187            collection_index_ref: scope()?,
1188            collection_security_generation: CollectionSecurityGeneration::new(1),
1189            authorized_scope_digest: AuthorizedScopeDigestV1::new([1; 32])?,
1190            policy_generation: PolicyGeneration::new(1),
1191            collection_reset_generation: CollectionResetGenerationV1::new(1)?,
1192            deletion_overlay_generation: DeletionOverlayGenerationV1::new(1)?,
1193            key_access_generation: KeyAccessGenerationV1::new(1)?,
1194            normalized_plan_digest: NormalizedPlanDigestV1::new([2; 32])?,
1195            output_semantics_digest: OutputSemanticsDigestV1::new([3; 32])?,
1196            expected_shard_count: 1,
1197            shard_generations: vec![CacheShardGenerationV1 {
1198                shard_id: ShardId::new(0),
1199                manifest_generation: ManifestGeneration::new(1),
1200            }],
1201            mapping_generation: SchemaGeneration::new(1),
1202            freshness: CacheFreshnessV1::Committed,
1203            reuse_scope: CacheReuseScopeV1::CompleteCommittedResult,
1204            from: 0,
1205            limit: 10,
1206            feature_flags_digest: FeatureFlagsDigestV1::new([4; 32])?,
1207            snapshot: CacheSnapshotBindingV1::Current,
1208        })
1209    }
1210
1211    #[test]
1212    fn cache_key_is_exact_and_realtime_is_subresult_only() -> Result<(), Box<dyn Error>> {
1213        let key = cache_key()?;
1214        let bytes = encode_result_cache_key_v1(&key)?;
1215        assert_eq!(decode_result_cache_key_v1(&bytes)?, key);
1216        let mut stale = key.clone();
1217        stale.freshness = CacheFreshnessV1::Realtime;
1218        assert!(matches!(stale.validate(), Err(DerivedContractError::Violation(_))));
1219        stale.reuse_scope = CacheReuseScopeV1::CommittedSubresultOfRealtime;
1220        stale.validate()?;
1221        let budget = ResultCacheBudgetV1 {
1222            format_version: DERIVED_OVERLOAD_FORMAT_VERSION,
1223            collection_id: CollectionId::new(1)?,
1224            maximum_entry_bytes: 1_024,
1225            maximum_collection_bytes: 4_096,
1226            maximum_collection_entries: 8,
1227        };
1228        let budget_bytes = encode_result_cache_budget_v1(&budget)?;
1229        assert_eq!(decode_result_cache_budget_v1(&budget_bytes)?, budget);
1230        let metrics = ResultCacheMetricsV1 {
1231            format_version: DERIVED_OVERLOAD_FORMAT_VERSION,
1232            collection_id: CollectionId::new(1)?,
1233            hits: 1,
1234            misses: 1,
1235            admissions: 1,
1236            bypasses: 1,
1237            evictions: 0,
1238            collection_bytes: 1_024,
1239            collection_entries: 1,
1240        };
1241        let metrics_bytes = encode_result_cache_metrics_v1(&metrics, budget)?;
1242        assert_eq!(decode_result_cache_metrics_v1(&metrics_bytes, budget)?, metrics);
1243        Ok(())
1244    }
1245
1246    #[test]
1247    fn promotion_is_physical_bounded_and_privacy_constrained() -> Result<(), Box<dyn Error>> {
1248        let heat = FieldHeatAggregateV1 {
1249            format_version: DERIVED_OVERLOAD_FORMAT_VERSION,
1250            collection_index_ref: scope()?,
1251            mapping_generation: SchemaGeneration::new(1),
1252            field_path_digest: FieldPathDigestV1::new([7; 32])?,
1253            window_start_logical_millis: LogicalMillis::new(1),
1254            window_end_logical_millis: LogicalMillis::new(2),
1255            filter_uses: 4,
1256            sort_uses: 1,
1257            aggregation_uses: 1,
1258            distinct_query_count: 4,
1259            estimated_cardinality: 100,
1260            estimated_extra_bytes: 1_024,
1261            privacy: PromotionPrivacyV1::Ordinary,
1262            field_type: PromotedFieldTypeV1::Keyword,
1263            observed_type_conflict: false,
1264            path_policy_eligible: true,
1265        };
1266        let policy = FieldPromotionPolicyV1 {
1267            format_version: DERIVED_OVERLOAD_FORMAT_VERSION,
1268            collection_index_ref: scope()?,
1269            mapping_generation: SchemaGeneration::new(1),
1270            policy_generation: PolicyGeneration::new(1),
1271            enabled: true,
1272            minimum_query_count: 3,
1273            maximum_promoted_fields: 4,
1274            maximum_cardinality: 1_000,
1275            maximum_extra_bytes: 2_048,
1276            allow_sensitive: false,
1277        };
1278        let policy_bytes = encode_field_promotion_policy_v1(&policy)?;
1279        assert_eq!(decode_field_promotion_policy_v1(&policy_bytes)?, policy);
1280        assert!(policy.admits(&heat, 0, 0)?);
1281        let mut restricted = heat.clone();
1282        restricted.privacy = PromotionPrivacyV1::Restricted;
1283        assert!(!policy.admits(&restricted, 0, 0)?);
1284        let mut conflict = heat.clone();
1285        conflict.observed_type_conflict = true;
1286        assert!(!policy.admits(&conflict, 0, 0)?);
1287        assert!(!policy.admits(&heat, policy.maximum_promoted_fields, 0)?);
1288        assert!(!policy.admits(&heat, 0, policy.maximum_extra_bytes)?);
1289        let mut stale_heat = heat.clone();
1290        stale_heat.mapping_generation = SchemaGeneration::new(2);
1291        assert!(matches!(
1292            policy.admits(&stale_heat, 0, 0),
1293            Err(DerivedContractError::Violation(_))
1294        ));
1295        let mut impossible_heat = heat.clone();
1296        impossible_heat.distinct_query_count = 7;
1297        assert!(matches!(
1298            impossible_heat.validate(),
1299            Err(DerivedContractError::Violation(_))
1300        ));
1301        let layout = PromotedFieldLayoutV1 {
1302            format_version: DERIVED_OVERLOAD_FORMAT_VERSION,
1303            collection_index_ref: scope()?,
1304            segment_id: SegmentId::new(1),
1305            field_path_digest: heat.field_path_digest,
1306            mapping_generation: heat.mapping_generation,
1307            decision_generation: PromotionDecisionGenerationV1::new(1)?,
1308            field_type: heat.field_type,
1309        };
1310        let layout_bytes = encode_promoted_field_layout_v1(&layout)?;
1311        assert_eq!(decode_promoted_field_layout_v1(&layout_bytes)?, layout);
1312        let status = FieldPromotionStatusV1 {
1313            format_version: DERIVED_OVERLOAD_FORMAT_VERSION,
1314            collection_index_ref: scope()?,
1315            mapping_generation: SchemaGeneration::new(1),
1316            policy_generation: PolicyGeneration::new(1),
1317            last_decision_generation: Some(PromotionDecisionGenerationV1::new(1)?),
1318            heat_records: 1,
1319            promoted_fields: 1,
1320            extra_bytes: 1_024,
1321            enabled: true,
1322        };
1323        let status_bytes = encode_field_promotion_status_v1(&status, policy)?;
1324        assert_eq!(decode_field_promotion_status_v1(&status_bytes, policy)?, status);
1325        let mut stale_status = status;
1326        stale_status.mapping_generation = SchemaGeneration::new(2);
1327        assert!(matches!(
1328            stale_status.validate(policy),
1329            Err(DerivedContractError::Violation(_))
1330        ));
1331        Ok(())
1332    }
1333
1334    #[test]
1335    fn promoted_column_sidecar_is_strict_current_and_segment_bound() -> Result<(), Box<dyn Error>> {
1336        let digest = field_path_digest_v1("service.latency")?;
1337        assert_eq!(digest, field_path_digest_v1("service.latency")?);
1338        assert_ne!(digest, field_path_digest_v1("service.duration")?);
1339        let sidecar = PromotedColumnSidecarV1 {
1340            format_version: DERIVED_OVERLOAD_FORMAT_VERSION,
1341            collection_index_ref: scope()?,
1342            segment_id: SegmentId::new(4),
1343            mapping_generation: SchemaGeneration::new(2),
1344            document_count: 3,
1345            columns: vec![PromotedFieldColumnV1 {
1346                layout: PromotedFieldLayoutV1 {
1347                    format_version: DERIVED_OVERLOAD_FORMAT_VERSION,
1348                    collection_index_ref: scope()?,
1349                    segment_id: SegmentId::new(4),
1350                    field_path_digest: digest,
1351                    mapping_generation: SchemaGeneration::new(2),
1352                    decision_generation: PromotionDecisionGenerationV1::new(3)?,
1353                    field_type: PromotedFieldTypeV1::U64,
1354                },
1355                rows: vec![
1356                    PromotedFieldRowV1 {
1357                        document_ordinal: 0,
1358                        value: PromotedFieldValueV1::U64(10),
1359                    },
1360                    PromotedFieldRowV1 {
1361                        document_ordinal: 2,
1362                        value: PromotedFieldValueV1::U64(20),
1363                    },
1364                ],
1365            }],
1366        };
1367        let bytes = encode_promoted_column_sidecar_v1(&sidecar)?;
1368        assert_eq!(decode_promoted_column_sidecar_v1(&bytes)?, sidecar);
1369
1370        let mut future = serde_json::from_slice::<serde_json::Value>(&bytes)?;
1371        future["format_version"] = serde_json::json!(DERIVED_OVERLOAD_FORMAT_VERSION.saturating_add(1));
1372        assert!(matches!(
1373            decode_promoted_column_sidecar_v1(&serde_json::to_vec(&future)?),
1374            Err(DerivedContractError::UnsupportedFormat(version))
1375                if version == DERIVED_OVERLOAD_FORMAT_VERSION + 1
1376        ));
1377        let mut unknown = serde_json::from_slice::<serde_json::Value>(&bytes)?;
1378        unknown["unknown"] = serde_json::json!(true);
1379        assert!(matches!(
1380            decode_promoted_column_sidecar_v1(&serde_json::to_vec(&unknown)?),
1381            Err(DerivedContractError::Serde(_))
1382        ));
1383        let current_version = format!("\"format_version\":{DERIVED_OVERLOAD_FORMAT_VERSION}");
1384        let duplicate_version = format!("{current_version},{current_version}");
1385        let duplicate = String::from_utf8(bytes.clone())?.replacen(&current_version, &duplicate_version, 1);
1386        assert!(matches!(
1387            decode_promoted_column_sidecar_v1(duplicate.as_bytes()),
1388            Err(DerivedContractError::Serde(_))
1389        ));
1390        let mut wrong_type = sidecar.clone();
1391        wrong_type.columns[0].rows[0].value = PromotedFieldValueV1::Keyword("10".to_owned());
1392        assert!(matches!(
1393            encode_promoted_column_sidecar_v1(&wrong_type),
1394            Err(DerivedContractError::Violation(_))
1395        ));
1396        let mut negative_zero = sidecar.clone();
1397        negative_zero.columns[0].layout.field_type = PromotedFieldTypeV1::F64;
1398        negative_zero.columns[0].rows[0].value = PromotedFieldValueV1::F64Bits((-0.0_f64).to_bits());
1399        negative_zero.columns[0].rows[1].value = PromotedFieldValueV1::F64Bits(1.0_f64.to_bits());
1400        assert!(matches!(
1401            encode_promoted_column_sidecar_v1(&negative_zero),
1402            Err(DerivedContractError::Violation(_))
1403        ));
1404        let mut empty = sidecar.clone();
1405        empty.columns.clear();
1406        assert!(matches!(
1407            encode_promoted_column_sidecar_v1(&empty),
1408            Err(DerivedContractError::Violation(_))
1409        ));
1410        let mut duplicate_generation = sidecar.clone();
1411        let mut duplicate_column = duplicate_generation.columns[0].clone();
1412        duplicate_column.layout.field_path_digest = field_path_digest_v1("service.duration")?;
1413        duplicate_generation.columns.push(duplicate_column);
1414        assert!(matches!(
1415            encode_promoted_column_sidecar_v1(&duplicate_generation),
1416            Err(DerivedContractError::Violation(_))
1417        ));
1418        let mut unordered = sidecar;
1419        unordered.columns[0].rows.swap(0, 1);
1420        assert!(matches!(
1421            encode_promoted_column_sidecar_v1(&unordered),
1422            Err(DerivedContractError::Violation(_))
1423        ));
1424        Ok(())
1425    }
1426
1427    #[test]
1428    fn overload_buffer_never_becomes_a_second_authority() -> Result<(), Box<dyn Error>> {
1429        let policy = OverloadPolicyV1 {
1430            format_version: DERIVED_OVERLOAD_FORMAT_VERSION,
1431            collection_index_ref: scope()?,
1432            policy_generation: PolicyGeneration::new(1),
1433            mode: OverloadModeV1::Buffer,
1434            maximum_backlog_bytes: 1_024,
1435            maximum_backlog_age_millis: 100,
1436            maximum_collection_share_bytes: 512,
1437            maximum_recovery_ops_per_pass: 100,
1438            maximum_pending_overlay_documents: 100,
1439        };
1440        let policy_bytes = encode_overload_policy_v1(&policy)?;
1441        assert_eq!(decode_overload_policy_v1(&policy_bytes)?, policy);
1442        let push_authority = IngestAuthorityV1::push_with_wal();
1443        assert_eq!(
1444            overload_disposition_v1(policy, IndexMode::AppendLog, &push_authority, false)?,
1445            OverloadDispositionV1::BufferRegisteredWal
1446        );
1447        assert_eq!(
1448            overload_disposition_v1(policy, IndexMode::Mutable, &push_authority, false)?,
1449            OverloadDispositionV1::RefuseMissingMutableOverlay
1450        );
1451        assert_eq!(
1452            overload_disposition_v1(policy, IndexMode::State, &push_authority, true)?,
1453            OverloadDispositionV1::RefuseStateMode
1454        );
1455        let invalid_authority = IngestAuthorityV1::PushWithWal { format_version: 3 };
1456        assert!(matches!(
1457            overload_disposition_v1(policy, IndexMode::AppendLog, &invalid_authority, false),
1458            Err(DerivedContractError::Source(_))
1459        ));
1460        let overlay = PendingMutationOverlayV1 {
1461            format_version: DERIVED_OVERLOAD_FORMAT_VERSION,
1462            shard_key: ShardKey::new(scope()?, ShardId::new(0)),
1463            writer_epoch: Epoch::new(1),
1464            registered_through: SeqNo::new(7),
1465            indexed_through: SeqNo::new(3),
1466            get_overlay_through: SeqNo::new(7),
1467            conditional_write_through: SeqNo::new(7),
1468            collector_visibility_through: SeqNo::new(7),
1469            subscription_through: SeqNo::new(7),
1470            pending_documents: 4,
1471            pending_tombstones: 2,
1472            pending_bytes: 128,
1473            oldest_age_millis: 10,
1474        };
1475        overlay.validate()?;
1476        assert_eq!(overlay.lag_operations()?, 4);
1477        let mut inconsistent_zero_lag = overlay;
1478        inconsistent_zero_lag.indexed_through = inconsistent_zero_lag.registered_through;
1479        assert!(matches!(
1480            inconsistent_zero_lag.validate(),
1481            Err(DerivedContractError::Violation(_))
1482        ));
1483        assert_eq!(
1484            overload_shed_reason_v1(policy, 100, 10, 100, 101, 4)?,
1485            Some(OverloadShedReasonV1::RecoveryWork)
1486        );
1487        let status = OverloadStatusV1 {
1488            format_version: DERIVED_OVERLOAD_FORMAT_VERSION,
1489            shard_key: ShardKey::new(scope()?, ShardId::new(0)),
1490            writer_epoch: Epoch::new(1),
1491            configured_mode: OverloadModeV1::Buffer,
1492            effective_disposition: OverloadDispositionV1::BufferRegisteredWal,
1493            registered_through: SeqNo::new(7),
1494            indexed_through: SeqNo::new(3),
1495            lag_operations: 4,
1496            backlog_bytes: 100,
1497            oldest_age_millis: 10,
1498            collection_share_bytes: 100,
1499            recovery_ops_this_pass: 10,
1500            pending_overlay_documents: 4,
1501            shed_reason: None,
1502        };
1503        let status_bytes = encode_overload_status_v1(&status, policy, IndexMode::AppendLog, &push_authority, false)?;
1504        assert_eq!(
1505            decode_overload_status_v1(&status_bytes, policy, IndexMode::AppendLog, &push_authority, false,)?,
1506            status
1507        );
1508        let mut false_state_status = status;
1509        false_state_status.effective_disposition = OverloadDispositionV1::BufferRegisteredWal;
1510        assert!(matches!(
1511            false_state_status.validate(policy, IndexMode::State, &push_authority, true),
1512            Err(DerivedContractError::Violation(_))
1513        ));
1514        let mut impossible_share = status;
1515        impossible_share.collection_share_bytes = impossible_share.backlog_bytes + 1;
1516        assert!(matches!(
1517            impossible_share.validate(policy, IndexMode::AppendLog, &push_authority, false),
1518            Err(DerivedContractError::Violation(_))
1519        ));
1520        Ok(())
1521    }
1522
1523    #[test]
1524    fn codec_refuses_future_unknown_duplicate_and_noncanonical() -> Result<(), Box<dyn Error>> {
1525        let bytes = encode_result_cache_key_v1(&cache_key()?)?;
1526        let mut future: serde_json::Value = serde_json::from_slice(&bytes)?;
1527        future["format_version"] = serde_json::Value::from(DERIVED_OVERLOAD_FORMAT_VERSION.saturating_add(1));
1528        assert_eq!(
1529            decode_result_cache_key_v1(&serde_json::to_vec(&future)?),
1530            Err(DerivedContractError::UnsupportedFormat(
1531                DERIVED_OVERLOAD_FORMAT_VERSION + 1
1532            ))
1533        );
1534        let mut unknown: serde_json::Value = serde_json::from_slice(&bytes)?;
1535        unknown["unknown_cache_key"] = serde_json::Value::Bool(true);
1536        assert!(matches!(
1537            decode_result_cache_key_v1(&serde_json::to_vec(&unknown)?),
1538            Err(DerivedContractError::Serde(_))
1539        ));
1540        let text = String::from_utf8(bytes)?;
1541        let current_version = format!("\"format_version\":{DERIVED_OVERLOAD_FORMAT_VERSION}");
1542        let duplicate_version = format!("{current_version},{current_version}");
1543        let duplicate = text.replacen(&current_version, &duplicate_version, 1);
1544        assert!(matches!(
1545            decode_result_cache_key_v1(duplicate.as_bytes()),
1546            Err(DerivedContractError::Serde(_))
1547        ));
1548        let spaced = format!(" {text}");
1549        assert_eq!(
1550            decode_result_cache_key_v1(spaced.as_bytes()),
1551            Err(DerivedContractError::NonCanonical)
1552        );
1553        Ok(())
1554    }
1555}