Skip to main content

acyclic_fs/
distributed.rs

1//! Filesystem storage bindings over the canonical public service providers.
2
3use crate::cancellation::CancellationToken;
4use crate::foundation::{
5    AuthorityId, Digest, DurableCommit, Epoch, GenerationId, Head, OperationId, ProposedCommit,
6    Sequence, authority_commit_digest,
7};
8use crate::kernel::{decode_published_generation, decode_volume_created, volume_authority_id};
9use crate::performance::{OperationFailure, WorkBudget, WorkCounters, WorkError};
10use crate::storage::{
11    AppendOutcome as FsAppendOutcome, AuthorityReceipt, AuthorityResult, AuthorityStoreError,
12    CreateAuthorityOutcome, FenceOutcome, ObjectId, ObjectRead, ObjectReadRequest,
13    ObjectReadRetention, ObjectReceipt, ObjectResult, ObjectStoreError, ObjectWrite,
14    PublicationPermit, PublicationReservation, ReplayLimit, ReservationOutcome, object_digest,
15};
16use crate::streams_record::StreamsDurableRecord;
17use crate::{AsyncAuthorityStore, AsyncObjectStore};
18use acyclic_objects::{
19    Condition, GetRequest, ObjectsError, ObjectsProvider, PutRequest, ReadTarget, wire,
20};
21use bytes::Bytes;
22use futures::StreamExt;
23use std::collections::BTreeMap;
24use std::sync::Arc;
25
26const STREAM_RECORD_LIMIT: u64 = acyclic_stream::MAX_RECORD_BYTES as u64;
27const GENESIS_DOMAIN: &[u8] = b"acyclic-fs-stream-genesis-v1\0";
28const EPOCH_DOMAIN: &[u8] = b"acyclic-fs-stream-epoch-v1\0";
29const LINEAGE_DOMAIN: &[u8] = b"acyclic-fs-stream-lineage-v1\0";
30const LINEAGE_TAIL_DOMAIN: &[u8] = b"acyclic-fs-stream-lineage-tail-v1\0";
31const AUTHORITY_MARKER: &[u8] = b"acyclic-fs-authority-v1\0";
32const PUBLICATION_GATE_FREE: &[u8] = b"acyclic-fs-publication-gate-v1\0free";
33const PUBLICATION_GATE_ACTIVE: &[u8] = b"acyclic-fs-publication-gate-v1\0active\0";
34
35/// Filesystem authority over native hierarchical Streams.
36///
37/// Records, writer epochs, and lineage are child paths changed through one atomic Stream commit.
38/// Retry results come from the provider's native idempotency authority; the adapter owns no side
39/// database or shadow operation streams.
40#[derive(Clone)]
41pub struct StreamAuthorityStore<P> {
42    provider: Arc<P>,
43}
44
45impl<P> StreamAuthorityStore<P> {
46    /// Binds one authenticated public Stream provider.
47    #[must_use]
48    pub fn new(provider: Arc<P>) -> Self {
49        Self { provider }
50    }
51
52    /// Returns the exact public Stream provider that supplies this authority.
53    ///
54    /// Higher-level protocol adapters use this only for filesystem-adjacent
55    /// durable state machines, such as S3 multipart upload sessions. The
56    /// filesystem authority itself remains represented by canonical Stream
57    /// records and never by an adapter-local database.
58    #[must_use]
59    pub fn provider(&self) -> Arc<P> {
60        Arc::clone(&self.provider)
61    }
62}
63
64#[derive(Clone, Copy)]
65struct AuthoritySnapshot {
66    head: Head,
67    record_tail: u64,
68    epoch_tail: u64,
69}
70
71impl<P: acyclic_stream::StreamProvider> StreamAuthorityStore<P> {
72    /// Lists a bounded snapshot of exact filesystem authority identities.
73    ///
74    /// Authorities are native direct children in Stream; no filesystem record or object is read.
75    pub async fn authorities(&self, maximum: u32) -> Result<Vec<AuthorityId>, AuthorityStoreError> {
76        let request_limit = maximum.checked_add(1).ok_or_else(|| {
77            AuthorityStoreError::Rejected("authority listing bound is too large".to_owned())
78        })?;
79        let parent = acyclic_stream::StreamPath::new("fs/authorities").map_err(map_stream_error)?;
80        let mut children = self
81            .provider
82            .children(acyclic_stream::ChildrenRequest {
83                parent: Some(parent),
84                limit: request_limit,
85            })
86            .await
87            .map_err(map_stream_error)?;
88        let mut authorities = Vec::new();
89        while let Some(child) = children.next().await {
90            let child = child.map_err(map_stream_error)?;
91            let encoded = child
92                .path
93                .as_str()
94                .strip_prefix("fs/authorities/")
95                .ok_or_else(|| {
96                    AuthorityStoreError::Corrupt(
97                        "Stream authority child escaped its parent".to_owned(),
98                    )
99                })?;
100            if encoded.len() != 32 || encoded.contains('/') {
101                return Err(AuthorityStoreError::Corrupt(
102                    "Stream authority child has a noncanonical identity".to_owned(),
103                ));
104            }
105            let mut bytes = [0_u8; 16];
106            hex::decode_to_slice(encoded, &mut bytes).map_err(|_| {
107                AuthorityStoreError::Corrupt(
108                    "Stream authority child has a noncanonical identity".to_owned(),
109                )
110            })?;
111            authorities.push(AuthorityId::from_bytes(bytes));
112        }
113        if authorities.len() > maximum as usize {
114            return Err(AuthorityStoreError::Rejected(
115                "authority listing bound exceeded".to_owned(),
116            ));
117        }
118        Ok(authorities)
119    }
120
121    async fn snapshot(
122        &self,
123        authority_id: AuthorityId,
124    ) -> Result<AuthoritySnapshot, AuthorityStoreError> {
125        let records = records_path(authority_id)?;
126        let epochs = epochs_path(authority_id)?;
127        let record_tail = self
128            .provider
129            .tail(records.clone())
130            .await
131            .map_err(map_stream_error)?;
132        let epoch_tail = self
133            .provider
134            .tail(epochs.clone())
135            .await
136            .map_err(map_stream_error)?;
137        if record_tail == 0 || epoch_tail == 0 {
138            return Err(AuthorityStoreError::Corrupt(
139                "authority genesis paths are empty".to_owned(),
140            ));
141        }
142        let epoch_record = read_one(self.provider.as_ref(), epochs, epoch_tail - 1).await?;
143        let epoch = decode_epoch(&epoch_record.value, EPOCH_DOMAIN)?;
144        let head = if record_tail == 1 {
145            let genesis = read_one(self.provider.as_ref(), records, 0).await?;
146            let _genesis_epoch = decode_epoch(&genesis.value, GENESIS_DOMAIN)?;
147            Head::genesis(epoch)
148        } else {
149            let record = read_one(self.provider.as_ref(), records, record_tail - 1).await?;
150            let durable = decode_durable(authority_id, &record.value)?;
151            if durable.sequence.get().saturating_add(1) != record_tail {
152                return Err(AuthorityStoreError::Corrupt(
153                    "Stream sequence does not match filesystem authority sequence".to_owned(),
154                ));
155            }
156            Head {
157                epoch,
158                sequence: durable.sequence,
159                digest: durable.digest,
160            }
161        };
162        Ok(AuthoritySnapshot {
163            head,
164            record_tail,
165            epoch_tail,
166        })
167    }
168
169    async fn publication_gate(
170        &self,
171        authority_id: AuthorityId,
172    ) -> Result<(u64, Option<OperationId>), AuthorityStoreError> {
173        let path = publication_gate_path(authority_id)?;
174        let tail = self
175            .provider
176            .tail(path.clone())
177            .await
178            .map_err(map_stream_error)?;
179        if tail == 0 {
180            return Err(AuthorityStoreError::Corrupt(
181                "authority publication gate is empty".to_owned(),
182            ));
183        }
184        let record = read_one(self.provider.as_ref(), path, tail - 1).await?;
185        Ok((tail, decode_publication_gate(&record.value)?))
186    }
187
188    async fn ensure_publication_gate(
189        &self,
190        authority_id: AuthorityId,
191        snapshot: AuthoritySnapshot,
192        mut work: WorkCounters,
193        budget: WorkBudget,
194    ) -> Result<WorkCounters, OperationFailure<AuthorityStoreError>> {
195        let path = publication_gate_path(authority_id).map_err(OperationFailure::before_work)?;
196        work = work
197            .checked_add(authority_read_work(1))
198            .map_err(|error| OperationFailure::new(error.into(), work))?;
199        admit_authority(work, budget)?;
200        let tail = match self.provider.tail(path.clone()).await {
201            Ok(tail) => tail,
202            Err(acyclic_stream::StreamError::NotFound) => 0,
203            Err(error) => {
204                return Err(OperationFailure::new(map_stream_error(error), work));
205            }
206        };
207        if tail != 0 {
208            work = work
209                .checked_add(authority_read_work(1))
210                .map_err(|error| OperationFailure::new(error.into(), work))?;
211            admit_authority(work, budget)?;
212            let record = read_one(self.provider.as_ref(), path, tail - 1)
213                .await
214                .map_err(|error| OperationFailure::new(error, work))?;
215            decode_publication_gate(&record.value)
216                .map_err(|error| OperationFailure::new(error, work))?;
217            return Ok(work);
218        }
219        let records =
220            records_path(authority_id).map_err(|error| OperationFailure::new(error, work))?;
221        let epochs =
222            epochs_path(authority_id).map_err(|error| OperationFailure::new(error, work))?;
223        let mut migration_identity = Vec::with_capacity(32);
224        migration_identity.extend_from_slice(&authority_id.into_bytes());
225        migration_identity.extend_from_slice(&snapshot.record_tail.to_le_bytes());
226        migration_identity.extend_from_slice(&snapshot.epoch_tail.to_le_bytes());
227        let request = acyclic_stream::CommitRequest {
228            conditions: vec![
229                acyclic_stream::CommitCondition::Tail {
230                    path: records,
231                    expected: snapshot.record_tail,
232                },
233                acyclic_stream::CommitCondition::Tail {
234                    path: epochs,
235                    expected: snapshot.epoch_tail,
236                },
237                acyclic_stream::CommitCondition::Absent { path: path.clone() },
238            ],
239            mutations: vec![acyclic_stream::CommitMutation::Append {
240                path: path.clone(),
241                records: vec![Bytes::from_static(PUBLICATION_GATE_FREE)],
242            }],
243            idempotency_key: stream_key(b"publication-gate", &migration_identity)
244                .map_err(|error| OperationFailure::new(error, work))?,
245        };
246        let mutation_work = work
247            .checked_add(authority_write_work(
248                1,
249                u64::try_from(PUBLICATION_GATE_FREE.len()).unwrap_or(u64::MAX),
250            ))
251            .map_err(|error| OperationFailure::new(error.into(), work))?;
252        let completed_work = mutation_work
253            .checked_add(authority_read_work(2))
254            .map_err(|error| OperationFailure::new(error.into(), mutation_work))?;
255        admit_authority(completed_work, budget)?;
256        match self.provider.commit(request).await {
257            Ok(
258                acyclic_stream::CommitOutcome::Committed(_)
259                | acyclic_stream::CommitOutcome::Conflict(_),
260            ) => {}
261            Err(error) => {
262                return Err(OperationFailure::new(
263                    map_stream_error(error),
264                    mutation_work,
265                ));
266            }
267        }
268        let tail = self
269            .provider
270            .tail(path.clone())
271            .await
272            .map_err(|error| OperationFailure::new(map_stream_error(error), completed_work))?;
273        if tail == 0 {
274            return Err(OperationFailure::new(
275                AuthorityStoreError::Rejected(
276                    "authority publication gate migration conflicted".to_owned(),
277                ),
278                completed_work,
279            ));
280        }
281        let record = read_one(self.provider.as_ref(), path, tail - 1)
282            .await
283            .map_err(|error| OperationFailure::new(error, completed_work))?;
284        decode_publication_gate(&record.value)
285            .map_err(|error| OperationFailure::new(error, completed_work))?;
286        Ok(completed_work)
287    }
288
289    async fn operation(
290        &self,
291        authority_id: AuthorityId,
292        operation_id: OperationId,
293    ) -> Result<Option<DurableCommit>, AuthorityStoreError> {
294        let key = operation_key(authority_id, operation_id)?;
295        let Some(observation) = self
296            .provider
297            .inspect_idempotency(key)
298            .await
299            .map_err(map_stream_error)?
300        else {
301            return Ok(None);
302        };
303        let acyclic_stream::IdempotencyOutcome::Commit(outcome) = observation.outcome else {
304            return Err(AuthorityStoreError::Corrupt(
305                "operation identity retained another Stream mutation family".to_owned(),
306            ));
307        };
308        let acyclic_stream::CommitOutcome::Committed(envelope) = outcome else {
309            return Ok(None);
310        };
311        durable_from_operation_envelope(authority_id, operation_id, envelope).map(Some)
312    }
313
314    async fn existing_fork_destination(
315        &self,
316        destination_authority: AuthorityId,
317        source_generation: GenerationId,
318        forked_at: u64,
319    ) -> Result<Option<AuthoritySnapshot>, AuthorityStoreError> {
320        let snapshot = match self.snapshot(destination_authority).await {
321            Ok(snapshot) => snapshot,
322            Err(AuthorityStoreError::Missing) => return Ok(None),
323            Err(error) => return Err(error),
324        };
325        let locator = generation_path(destination_authority, source_generation)?;
326        let record = read_one(self.provider.as_ref(), locator, 0).await?;
327        let located_tail = decode_lineage_tail(&record.value)?;
328        if located_tail == 0 {
329            return Err(AuthorityStoreError::Corrupt(
330                "destination generation locator selected an empty lineage".to_owned(),
331            ));
332        }
333        let lineage = lineage_path(destination_authority)?;
334        let selected = read_one(self.provider.as_ref(), lineage, located_tail - 1).await?;
335        if located_tail == forked_at
336            && decode_lineage_generation(&selected.value)? == source_generation
337        {
338            Ok(Some(snapshot))
339        } else {
340            Err(AuthorityStoreError::Rejected(
341                "destination generation fork conflicts with existing state".to_owned(),
342            ))
343        }
344    }
345
346    /// Resolves the source lineage path and fork tail for one generation fork.
347    async fn resolve_source_fork_point(
348        &self,
349        source_authority: AuthorityId,
350        source_generation: GenerationId,
351    ) -> Result<(acyclic_stream::StreamPath, u64), OperationFailure<AuthorityStoreError>> {
352        let source_lineage =
353            lineage_path(source_authority).map_err(OperationFailure::before_work)?;
354        let source_locator = generation_path(source_authority, source_generation)
355            .map_err(OperationFailure::before_work)?;
356        let locator_record = match read_one(self.provider.as_ref(), source_locator, 0).await {
357            Ok(record) => record,
358            Err(AuthorityStoreError::Missing) => {
359                return Err(OperationFailure::before_work(
360                    AuthorityStoreError::Rejected(
361                        "source authority has no generation lineage locator".to_owned(),
362                    ),
363                ));
364            }
365            Err(error) => return Err(OperationFailure::before_work(error)),
366        };
367        let forked_at =
368            decode_lineage_tail(&locator_record.value).map_err(OperationFailure::before_work)?;
369        if forked_at == 0 {
370            return Err(OperationFailure::before_work(AuthorityStoreError::Corrupt(
371                "generation locator selected an empty lineage".to_owned(),
372            )));
373        }
374        let selected = read_one(
375            self.provider.as_ref(),
376            source_lineage.clone(),
377            forked_at - 1,
378        )
379        .await
380        .map_err(OperationFailure::before_work)?;
381        if decode_lineage_generation(&selected.value).map_err(OperationFailure::before_work)?
382            != source_generation
383        {
384            return Err(OperationFailure::before_work(AuthorityStoreError::Corrupt(
385                "generation locator and lineage disagree".to_owned(),
386            )));
387        }
388        Ok((source_lineage, forked_at))
389    }
390}
391
392impl<P: acyclic_stream::StreamProvider> AsyncAuthorityStore for StreamAuthorityStore<P> {
393    fn supports_generation_lineage_prefix(&self) -> bool {
394        true
395    }
396
397    async fn fork_generation_authority(
398        &self,
399        source: crate::GenerationForkSource,
400        destination_authority: AuthorityId,
401        operation_id: OperationId,
402        budget: WorkBudget,
403        cancellation: &CancellationToken,
404    ) -> AuthorityResult<CreateAuthorityOutcome> {
405        cancellation
406            .check()
407            .map_err(|_| OperationFailure::before_work(AuthorityStoreError::Cancelled))?;
408        if source.lineage == crate::GenerationFork::Independent {
409            return self
410                .create_authority(destination_authority, Epoch::GENESIS, budget, cancellation)
411                .await;
412        }
413        let (source_lineage, forked_at) = self
414            .resolve_source_fork_point(source.authority, source.generation)
415            .await?;
416        let destination_records =
417            records_path(destination_authority).map_err(OperationFailure::before_work)?;
418        let destination_root =
419            authority_path(destination_authority).map_err(OperationFailure::before_work)?;
420        let destination_epochs =
421            epochs_path(destination_authority).map_err(OperationFailure::before_work)?;
422        let destination_lineage =
423            lineage_path(destination_authority).map_err(OperationFailure::before_work)?;
424        let destination_gate =
425            publication_gate_path(destination_authority).map_err(OperationFailure::before_work)?;
426        let destination_locator = generation_path(destination_authority, source.generation)
427            .map_err(OperationFailure::before_work)?;
428        if let Some(snapshot) = self
429            .existing_fork_destination(destination_authority, source.generation, forked_at)
430            .await
431            .map_err(OperationFailure::before_work)?
432        {
433            let work = self
434                .ensure_publication_gate(
435                    destination_authority,
436                    snapshot,
437                    authority_read_work(8),
438                    budget,
439                )
440                .await?;
441            return authority_success(
442                CreateAuthorityOutcome::Existing(snapshot.head),
443                work,
444                budget,
445            );
446        }
447        let request = fork_generation_commit_request(
448            ForkDestinationPaths {
449                root: destination_root,
450                records: destination_records,
451                epochs: destination_epochs,
452                locator: destination_locator,
453                lineage: destination_lineage,
454                gate: destination_gate,
455            },
456            source_lineage,
457            forked_at,
458            operation_id,
459        )?;
460        let mut work = authority_write_work(4, 96);
461        work.authority_records_read = 3;
462        work.backend_read_operations = 3;
463        admit_authority(work, budget)?;
464        match self.provider.commit(request).await {
465            Ok(acyclic_stream::CommitOutcome::Committed(_)) => authority_success(
466                CreateAuthorityOutcome::Created(Head::genesis(Epoch::GENESIS)),
467                work,
468                budget,
469            ),
470            Ok(acyclic_stream::CommitOutcome::Conflict(_)) => {
471                work = work
472                    .checked_add(authority_read_work(6))
473                    .map_err(|error| OperationFailure::new(error.into(), work))?;
474                let snapshot = self
475                    .existing_fork_destination(destination_authority, source.generation, forked_at)
476                    .await
477                    .map_err(|error| OperationFailure::new(error, work))?
478                    .ok_or_else(|| {
479                        OperationFailure::new(
480                            AuthorityStoreError::Rejected(
481                                "generation fork conflicted without creating its destination"
482                                    .to_owned(),
483                            ),
484                            work,
485                        )
486                    })?;
487                work = self
488                    .ensure_publication_gate(destination_authority, snapshot, work, budget)
489                    .await?;
490                authority_success(
491                    CreateAuthorityOutcome::Existing(snapshot.head),
492                    work,
493                    budget,
494                )
495            }
496            Err(error) => Err(OperationFailure::new(map_stream_error(error), work)),
497        }
498    }
499
500    async fn create_authority(
501        &self,
502        authority_id: AuthorityId,
503        genesis_epoch: Epoch,
504        budget: WorkBudget,
505        cancellation: &CancellationToken,
506    ) -> AuthorityResult<CreateAuthorityOutcome> {
507        cancellation
508            .check()
509            .map_err(|_| OperationFailure::before_work(AuthorityStoreError::Cancelled))?;
510        match self.snapshot(authority_id).await {
511            Ok(snapshot) => {
512                let work = self
513                    .ensure_publication_gate(authority_id, snapshot, authority_read_work(2), budget)
514                    .await?;
515                return authority_success(
516                    CreateAuthorityOutcome::Existing(snapshot.head),
517                    work,
518                    budget,
519                );
520            }
521            Err(AuthorityStoreError::Missing) => {}
522            Err(error) => return Err(OperationFailure::before_work(error)),
523        }
524        let records = records_path(authority_id).map_err(OperationFailure::before_work)?;
525        let epochs = epochs_path(authority_id).map_err(OperationFailure::before_work)?;
526        let root = authority_path(authority_id).map_err(OperationFailure::before_work)?;
527        let gate = publication_gate_path(authority_id).map_err(OperationFailure::before_work)?;
528        let request = acyclic_stream::CommitRequest {
529            conditions: vec![
530                acyclic_stream::CommitCondition::Absent { path: root.clone() },
531                acyclic_stream::CommitCondition::Absent {
532                    path: records.clone(),
533                },
534                acyclic_stream::CommitCondition::Absent {
535                    path: epochs.clone(),
536                },
537                acyclic_stream::CommitCondition::Absent { path: gate.clone() },
538            ],
539            mutations: vec![
540                acyclic_stream::CommitMutation::Append {
541                    path: root,
542                    records: vec![Bytes::from_static(AUTHORITY_MARKER)],
543                },
544                acyclic_stream::CommitMutation::Append {
545                    path: records,
546                    records: vec![encode_epoch(GENESIS_DOMAIN, genesis_epoch)],
547                },
548                acyclic_stream::CommitMutation::Append {
549                    path: epochs,
550                    records: vec![encode_epoch(EPOCH_DOMAIN, genesis_epoch)],
551                },
552                acyclic_stream::CommitMutation::Append {
553                    path: gate,
554                    records: vec![Bytes::from_static(PUBLICATION_GATE_FREE)],
555                },
556            ],
557            idempotency_key: stream_key(b"create", &authority_id.into_bytes())
558                .map_err(OperationFailure::before_work)?,
559        };
560        let work = authority_write_work(2, 0);
561        admit_authority(work, budget)?;
562        match self.provider.commit(request).await {
563            Ok(acyclic_stream::CommitOutcome::Committed(_)) => authority_success(
564                CreateAuthorityOutcome::Created(Head::genesis(genesis_epoch)),
565                work,
566                budget,
567            ),
568            Ok(acyclic_stream::CommitOutcome::Conflict(_)) => {
569                let snapshot = self
570                    .snapshot(authority_id)
571                    .await
572                    .map_err(|error| OperationFailure::new(error, work))?;
573                authority_success(
574                    CreateAuthorityOutcome::Existing(snapshot.head),
575                    work,
576                    budget,
577                )
578            }
579            Err(error) => Err(OperationFailure::new(map_stream_error(error), work)),
580        }
581    }
582
583    async fn head(
584        &self,
585        authority_id: AuthorityId,
586        budget: WorkBudget,
587        cancellation: &CancellationToken,
588    ) -> AuthorityResult<Head> {
589        cancellation
590            .check()
591            .map_err(|_| OperationFailure::before_work(AuthorityStoreError::Cancelled))?;
592        let snapshot = self
593            .snapshot(authority_id)
594            .await
595            .map_err(OperationFailure::before_work)?;
596        authority_success(snapshot.head, authority_read_work(2), budget)
597    }
598
599    async fn compare_and_append(
600        &self,
601        authority_id: AuthorityId,
602        epoch: Epoch,
603        expected: Head,
604        commit: ProposedCommit,
605        budget: WorkBudget,
606        cancellation: &CancellationToken,
607    ) -> AuthorityResult<FsAppendOutcome> {
608        self.compare_and_append_guarded(
609            crate::GuardedAppend {
610                authority_id,
611                epoch,
612                expected,
613                commit,
614                permit: PublicationPermit::Unrestricted,
615            },
616            budget,
617            cancellation,
618        )
619        .await
620    }
621
622    #[allow(
623        clippy::too_many_lines,
624        reason = "one atomic Stream transaction keeps fencing, idempotency, lineage, and terminal mapping auditable together"
625    )]
626    async fn compare_and_append_guarded(
627        &self,
628        request: crate::GuardedAppend,
629        budget: WorkBudget,
630        cancellation: &CancellationToken,
631    ) -> AuthorityResult<FsAppendOutcome> {
632        let crate::GuardedAppend {
633            authority_id,
634            epoch,
635            expected,
636            commit,
637            permit,
638        } = request;
639        cancellation
640            .check()
641            .map_err(|_| OperationFailure::before_work(AuthorityStoreError::Cancelled))?;
642        if let Some(existing) = self
643            .operation(authority_id, commit.operation_id)
644            .await
645            .map_err(OperationFailure::before_work)?
646        {
647            let value = if existing.fingerprint == commit.fingerprint {
648                FsAppendOutcome::AlreadyCommitted(existing)
649            } else {
650                FsAppendOutcome::IdempotencyConflict {
651                    committed_fingerprint: existing.fingerprint,
652                }
653            };
654            return authority_success(value, authority_read_work(1), budget);
655        }
656        let snapshot = self
657            .snapshot(authority_id)
658            .await
659            .map_err(OperationFailure::before_work)?;
660        if snapshot.head.epoch != epoch {
661            return authority_success(
662                FsAppendOutcome::Fenced {
663                    actual_epoch: snapshot.head.epoch,
664                },
665                authority_read_work(2),
666                budget,
667            );
668        }
669        if snapshot.head != expected {
670            return authority_success(
671                FsAppendOutcome::Conflict {
672                    actual: snapshot.head,
673                },
674                authority_read_work(2),
675                budget,
676            );
677        }
678        let (gate_tail, active_reservation) = self
679            .publication_gate(authority_id)
680            .await
681            .map_err(OperationFailure::before_work)?;
682        let reservation_admitted = match permit {
683            PublicationPermit::Reservation {
684                operation_id,
685                gate_tail: permitted_tail,
686                expected: reserved_head,
687            } => {
688                permitted_tail == gate_tail
689                    && active_reservation == Some(OperationId::from_bytes(operation_id))
690                    && reserved_head == expected
691            }
692            PublicationPermit::Unrestricted => active_reservation.is_none(),
693            PublicationPermit::Lease {
694                authority_id: permitted_authority,
695                workspace_id,
696                ..
697            } => {
698                let permitted_workspace = crate::WorkspaceId::from_bytes(workspace_id);
699                let derived_authority =
700                    crate::kernel::volume_authority_id(permitted_workspace.volume_id());
701                active_reservation.is_none()
702                    && AuthorityId::from_bytes(permitted_authority) == authority_id
703                    && derived_authority == authority_id
704            }
705        };
706        if !reservation_admitted {
707            return authority_success(
708                FsAppendOutcome::Fenced {
709                    actual_epoch: snapshot.head.epoch,
710                },
711                authority_read_work(3),
712                budget,
713            );
714        }
715        let sequence = expected
716            .sequence
717            .checked_next()
718            .map_err(|_| OperationFailure::before_work(AuthorityStoreError::SequenceExhausted))?;
719        let durable = DurableCommit {
720            epoch,
721            sequence,
722            operation_id: commit.operation_id,
723            fingerprint: commit.fingerprint,
724            previous_digest: expected.digest,
725            digest: authority_commit_digest(
726                authority_id,
727                epoch,
728                sequence,
729                commit.operation_id,
730                commit.fingerprint,
731                expected.digest,
732                &commit.payload,
733            ),
734            payload: commit.payload,
735        };
736        let encoded =
737            StreamsDurableRecord::encode(&durable, STREAM_RECORD_LIMIT).map_err(|error| {
738                OperationFailure::before_work(AuthorityStoreError::Rejected(error.to_string()))
739            })?;
740        let records = records_path(authority_id).map_err(OperationFailure::before_work)?;
741        let epochs = epochs_path(authority_id).map_err(OperationFailure::before_work)?;
742        let gate = publication_gate_path(authority_id).map_err(OperationFailure::before_work)?;
743        let mut conditions = vec![
744            acyclic_stream::CommitCondition::Tail {
745                path: records.clone(),
746                expected: snapshot.record_tail,
747            },
748            acyclic_stream::CommitCondition::Tail {
749                path: epochs,
750                expected: snapshot.epoch_tail,
751            },
752            acyclic_stream::CommitCondition::Tail {
753                path: gate.clone(),
754                expected: gate_tail,
755            },
756        ];
757        let mut lease_deadline = None;
758        let lease_path = match permit {
759            PublicationPermit::Unrestricted | PublicationPermit::Reservation { .. } => None,
760            PublicationPermit::Lease {
761                authority_id: _,
762                workspace_id,
763                lease_id,
764                expires_at_millis,
765            } => {
766                lease_deadline = Some(expires_at_millis);
767                let path = crate::operation_window::stream_lease_path(
768                    crate::WorkspaceId::from_bytes(workspace_id),
769                    crate::OperationLeaseId::from_bytes(lease_id),
770                )
771                .map_err(|error| OperationFailure::before_work(map_stream_error(error)))?;
772                conditions.push(acyclic_stream::CommitCondition::Tail {
773                    path: path.clone(),
774                    expected: 1,
775                });
776                Some(path)
777            }
778        };
779        let mut mutations = vec![acyclic_stream::CommitMutation::Append {
780            path: records,
781            records: vec![encoded],
782        }];
783        let mut authority_records = 1_u64;
784        let mut authority_bytes = u64::try_from(durable.payload.len()).unwrap_or(u64::MAX);
785        if let Some(generation) = generation_from_payload(authority_id, sequence, &durable.payload)
786            .map_err(OperationFailure::before_work)?
787        {
788            let lineage = lineage_path(authority_id).map_err(OperationFailure::before_work)?;
789            let locator =
790                generation_path(authority_id, generation).map_err(OperationFailure::before_work)?;
791            match self.provider.tail(locator.clone()).await {
792                Ok(1) => {
793                    let record = read_one(self.provider.as_ref(), locator, 0)
794                        .await
795                        .map_err(OperationFailure::before_work)?;
796                    let located_tail = decode_lineage_tail(&record.value)
797                        .map_err(OperationFailure::before_work)?;
798                    if located_tail == 0 {
799                        return Err(OperationFailure::before_work(AuthorityStoreError::Corrupt(
800                            "generation locator selected an empty lineage".to_owned(),
801                        )));
802                    }
803                    let lineage_record =
804                        read_one(self.provider.as_ref(), lineage, located_tail - 1)
805                            .await
806                            .map_err(OperationFailure::before_work)?;
807                    if decode_lineage_generation(&lineage_record.value)
808                        .map_err(OperationFailure::before_work)?
809                        != generation
810                    {
811                        return Err(OperationFailure::before_work(AuthorityStoreError::Corrupt(
812                            "generation locator does not identify its lineage record".to_owned(),
813                        )));
814                    }
815                }
816                Err(acyclic_stream::StreamError::NotFound) => {
817                    let lineage_tail = match self.provider.tail(lineage.clone()).await {
818                        Ok(tail) => tail,
819                        Err(acyclic_stream::StreamError::NotFound) => 0,
820                        Err(error) => {
821                            return Err(OperationFailure::before_work(map_stream_error(error)));
822                        }
823                    };
824                    conditions.push(if lineage_tail == 0 {
825                        acyclic_stream::CommitCondition::Absent {
826                            path: lineage.clone(),
827                        }
828                    } else {
829                        acyclic_stream::CommitCondition::Tail {
830                            path: lineage.clone(),
831                            expected: lineage_tail,
832                        }
833                    });
834                    conditions.push(acyclic_stream::CommitCondition::Absent {
835                        path: locator.clone(),
836                    });
837                    let lineage_record = encode_lineage_generation(generation);
838                    let locator_record = encode_lineage_tail(lineage_tail.saturating_add(1));
839                    authority_bytes = authority_bytes
840                        .saturating_add(u64::try_from(lineage_record.len()).unwrap_or(u64::MAX))
841                        .saturating_add(u64::try_from(locator_record.len()).unwrap_or(u64::MAX));
842                    mutations.push(acyclic_stream::CommitMutation::Append {
843                        path: lineage,
844                        records: vec![lineage_record],
845                    });
846                    mutations.push(acyclic_stream::CommitMutation::Append {
847                        path: locator,
848                        records: vec![locator_record],
849                    });
850                    authority_records = authority_records.saturating_add(2);
851                }
852                Ok(_) => {
853                    return Err(OperationFailure::before_work(AuthorityStoreError::Corrupt(
854                        "generation locator contains an invalid record count".to_owned(),
855                    )));
856                }
857                Err(error) => {
858                    return Err(OperationFailure::before_work(map_stream_error(error)));
859                }
860            }
861        }
862        let request = acyclic_stream::CommitRequest {
863            conditions,
864            mutations,
865            idempotency_key: operation_key(authority_id, durable.operation_id)
866                .map_err(OperationFailure::before_work)?,
867        };
868        let work = authority_write_work(authority_records, authority_bytes);
869        admit_authority(work, budget)?;
870        let commit_result = if let Some(deadline) = lease_deadline {
871            self.provider.commit_before(request, deadline).await
872        } else {
873            self.provider.commit(request).await
874        };
875        match commit_result {
876            Ok(acyclic_stream::CommitOutcome::Committed(_)) => {
877                authority_success(FsAppendOutcome::Committed(durable), work, budget)
878            }
879            Ok(acyclic_stream::CommitOutcome::Conflict(conflicts)) => {
880                if let Some(existing) = self
881                    .operation(authority_id, durable.operation_id)
882                    .await
883                    .map_err(|error| OperationFailure::new(error, work))?
884                {
885                    let value = if existing.fingerprint == durable.fingerprint {
886                        FsAppendOutcome::AlreadyCommitted(existing)
887                    } else {
888                        FsAppendOutcome::IdempotencyConflict {
889                            committed_fingerprint: existing.fingerprint,
890                        }
891                    };
892                    return authority_success(value, work, budget);
893                }
894                let actual = self
895                    .snapshot(authority_id)
896                    .await
897                    .map_err(|error| OperationFailure::new(error, work))?
898                    .head;
899                let gate_rejected = conflicts.iter().any(|conflict| match conflict {
900                    acyclic_stream::CommitConflict::Tail { path, .. }
901                    | acyclic_stream::CommitConflict::Exists { path }
902                    | acyclic_stream::CommitConflict::Retired { path } => path == &gate,
903                });
904                let permit_rejected = gate_rejected
905                    || lease_path.as_ref().is_some_and(|lease_path| {
906                        conflicts.iter().any(|conflict| match conflict {
907                            acyclic_stream::CommitConflict::Tail { path, .. }
908                            | acyclic_stream::CommitConflict::Exists { path }
909                            | acyclic_stream::CommitConflict::Retired { path } => {
910                                path == lease_path
911                            }
912                        })
913                    });
914                let value = if permit_rejected {
915                    FsAppendOutcome::Fenced {
916                        actual_epoch: actual.epoch,
917                    }
918                } else if actual.epoch == epoch {
919                    FsAppendOutcome::Conflict { actual }
920                } else {
921                    FsAppendOutcome::Fenced {
922                        actual_epoch: actual.epoch,
923                    }
924                };
925                authority_success(value, work, budget)
926            }
927            Err(acyclic_stream::StreamError::IdempotencyMismatch) => {
928                let existing = self
929                    .operation(authority_id, durable.operation_id)
930                    .await
931                    .map_err(|error| OperationFailure::new(error, work))?
932                    .ok_or_else(|| {
933                        OperationFailure::new(
934                            AuthorityStoreError::Corrupt(
935                                "Stream idempotency mismatch has no operation record".to_owned(),
936                            ),
937                            work,
938                        )
939                    })?;
940                authority_success(
941                    FsAppendOutcome::IdempotencyConflict {
942                        committed_fingerprint: existing.fingerprint,
943                    },
944                    work,
945                    budget,
946                )
947            }
948            Err(acyclic_stream::StreamError::DeadlineElapsed) => authority_success(
949                FsAppendOutcome::Fenced {
950                    actual_epoch: epoch,
951                },
952                work,
953                budget,
954            ),
955            Err(error) => Err(OperationFailure::new(map_stream_error(error), work)),
956        }
957    }
958
959    async fn reserve_publication(
960        &self,
961        authority_id: AuthorityId,
962        expected: Head,
963        operation_id: OperationId,
964        budget: WorkBudget,
965        cancellation: &CancellationToken,
966    ) -> AuthorityResult<ReservationOutcome> {
967        cancellation
968            .check()
969            .map_err(|_| OperationFailure::before_work(AuthorityStoreError::Cancelled))?;
970        let snapshot = self
971            .snapshot(authority_id)
972            .await
973            .map_err(OperationFailure::before_work)?;
974        if snapshot.head != expected {
975            return authority_success(
976                ReservationOutcome::Conflict {
977                    actual: snapshot.head,
978                },
979                authority_read_work(2),
980                budget,
981            );
982        }
983        let gate = publication_gate_path(authority_id).map_err(OperationFailure::before_work)?;
984        let (gate_tail, active) = self
985            .publication_gate(authority_id)
986            .await
987            .map_err(OperationFailure::before_work)?;
988        if active == Some(operation_id) {
989            return authority_success(
990                ReservationOutcome::AlreadyReserved(PublicationReservation {
991                    authority_id,
992                    operation_id,
993                    expected,
994                    gate_tail,
995                }),
996                authority_read_work(3),
997                budget,
998            );
999        }
1000        if active.is_some() {
1001            return authority_success(
1002                ReservationOutcome::Conflict {
1003                    actual: snapshot.head,
1004                },
1005                authority_read_work(3),
1006                budget,
1007            );
1008        }
1009        let records = records_path(authority_id).map_err(OperationFailure::before_work)?;
1010        let epochs = epochs_path(authority_id).map_err(OperationFailure::before_work)?;
1011        let request = acyclic_stream::CommitRequest {
1012            conditions: vec![
1013                acyclic_stream::CommitCondition::Tail {
1014                    path: records,
1015                    expected: snapshot.record_tail,
1016                },
1017                acyclic_stream::CommitCondition::Tail {
1018                    path: epochs,
1019                    expected: snapshot.epoch_tail,
1020                },
1021                acyclic_stream::CommitCondition::Tail {
1022                    path: gate.clone(),
1023                    expected: gate_tail,
1024                },
1025            ],
1026            mutations: vec![acyclic_stream::CommitMutation::Append {
1027                path: gate,
1028                records: vec![encode_active_reservation(operation_id)],
1029            }],
1030            idempotency_key: publication_reservation_key(
1031                b"reserve-publication",
1032                authority_id,
1033                operation_id,
1034                gate_tail,
1035            )
1036            .map_err(OperationFailure::before_work)?,
1037        };
1038        let work = authority_write_work(1, 16);
1039        admit_authority(work, budget)?;
1040        match self.provider.commit(request).await {
1041            Ok(acyclic_stream::CommitOutcome::Committed(_)) => authority_success(
1042                ReservationOutcome::Reserved(PublicationReservation {
1043                    authority_id,
1044                    operation_id,
1045                    expected,
1046                    gate_tail: gate_tail + 1,
1047                }),
1048                work,
1049                budget,
1050            ),
1051            Ok(acyclic_stream::CommitOutcome::Conflict(_)) => {
1052                let actual = self
1053                    .snapshot(authority_id)
1054                    .await
1055                    .map_err(|error| OperationFailure::new(error, work))?
1056                    .head;
1057                authority_success(ReservationOutcome::Conflict { actual }, work, budget)
1058            }
1059            Err(error) => Err(OperationFailure::new(map_stream_error(error), work)),
1060        }
1061    }
1062
1063    async fn release_publication(
1064        &self,
1065        reservation: PublicationReservation,
1066        budget: WorkBudget,
1067        cancellation: &CancellationToken,
1068    ) -> AuthorityResult<()> {
1069        cancellation
1070            .check()
1071            .map_err(|_| OperationFailure::before_work(AuthorityStoreError::Cancelled))?;
1072        let release_key = publication_reservation_key(
1073            b"release-publication",
1074            reservation.authority_id,
1075            reservation.operation_id,
1076            reservation.gate_tail,
1077        )
1078        .map_err(OperationFailure::before_work)?;
1079        // The atomic release can commit even when its response is lost. Check the
1080        // durable idempotency record before inspecting the now-free gate so an
1081        // exact retry remains successful after the visible gate has advanced.
1082        let mut work = authority_read_work(1);
1083        admit_authority(work, budget)?;
1084        if let Some(observation) = self
1085            .provider
1086            .inspect_idempotency(release_key.clone())
1087            .await
1088            .map_err(|error| OperationFailure::new(map_stream_error(error), work))?
1089        {
1090            return match observation.outcome {
1091                acyclic_stream::IdempotencyOutcome::Commit(
1092                    acyclic_stream::CommitOutcome::Committed(_),
1093                ) => authority_success((), work, budget),
1094                acyclic_stream::IdempotencyOutcome::Commit(
1095                    acyclic_stream::CommitOutcome::Conflict(_),
1096                ) => Err(OperationFailure::new(
1097                    AuthorityStoreError::Rejected(
1098                        "publication reservation is no longer active".to_owned(),
1099                    ),
1100                    work,
1101                )),
1102                _ => Err(OperationFailure::new(
1103                    AuthorityStoreError::Corrupt(
1104                        "publication release identity retained another Stream mutation family"
1105                            .to_owned(),
1106                    ),
1107                    work,
1108                )),
1109            };
1110        }
1111        let gate = publication_gate_path(reservation.authority_id)
1112            .map_err(|error| OperationFailure::new(error, work))?;
1113        work = work
1114            .checked_add(authority_read_work(2))
1115            .map_err(|error| OperationFailure::new(error.into(), work))?;
1116        admit_authority(work, budget)?;
1117        let (current_tail, active) = self
1118            .publication_gate(reservation.authority_id)
1119            .await
1120            .map_err(|error| OperationFailure::new(error, work))?;
1121        if current_tail != reservation.gate_tail || active != Some(reservation.operation_id) {
1122            return Err(OperationFailure::new(
1123                AuthorityStoreError::Rejected(
1124                    "publication reservation is no longer owned by this operation".to_owned(),
1125                ),
1126                work,
1127            ));
1128        }
1129        let request = acyclic_stream::CommitRequest {
1130            conditions: vec![acyclic_stream::CommitCondition::Tail {
1131                path: gate.clone(),
1132                expected: reservation.gate_tail,
1133            }],
1134            mutations: vec![acyclic_stream::CommitMutation::Append {
1135                path: gate,
1136                records: vec![Bytes::from_static(PUBLICATION_GATE_FREE)],
1137            }],
1138            idempotency_key: release_key,
1139        };
1140        work = work
1141            .checked_add(authority_write_work(1, 0))
1142            .map_err(|error| OperationFailure::new(error.into(), work))?;
1143        admit_authority(work, budget)?;
1144        match self.provider.commit(request).await {
1145            Ok(acyclic_stream::CommitOutcome::Committed(_)) => authority_success((), work, budget),
1146            Ok(acyclic_stream::CommitOutcome::Conflict(_)) => Err(OperationFailure::new(
1147                AuthorityStoreError::Rejected(
1148                    "publication reservation is no longer active".to_owned(),
1149                ),
1150                work,
1151            )),
1152            Err(acyclic_stream::StreamError::IdempotencyMismatch) => Err(OperationFailure::new(
1153                AuthorityStoreError::Rejected(
1154                    "publication reservation release identity was reused".to_owned(),
1155                ),
1156                work,
1157            )),
1158            Err(error) => Err(OperationFailure::new(map_stream_error(error), work)),
1159        }
1160    }
1161
1162    async fn replay(
1163        &self,
1164        authority_id: AuthorityId,
1165        after: Sequence,
1166        limit: ReplayLimit,
1167        budget: WorkBudget,
1168        cancellation: &CancellationToken,
1169    ) -> AuthorityResult<Vec<DurableCommit>> {
1170        cancellation
1171            .check()
1172            .map_err(|_| OperationFailure::before_work(AuthorityStoreError::Cancelled))?;
1173        if limit.records == 0 || limit.payload_bytes == 0 {
1174            return Err(OperationFailure::before_work(
1175                AuthorityStoreError::InvalidReplayLimit,
1176            ));
1177        }
1178        let path = records_path(authority_id).map_err(OperationFailure::before_work)?;
1179        let mut stream = self
1180            .provider
1181            .read(acyclic_stream::ReadRequest {
1182                path,
1183                from: after.get().saturating_add(1),
1184                limit: limit.records,
1185            })
1186            .await
1187            .map_err(|error| OperationFailure::before_work(map_stream_error(error)))?;
1188        let mut commits = Vec::new();
1189        let mut payload_bytes = 0_u64;
1190        while let Some(record) = stream.next().await {
1191            let record =
1192                record.map_err(|error| OperationFailure::before_work(map_stream_error(error)))?;
1193            let durable = decode_durable(authority_id, &record.value)
1194                .map_err(OperationFailure::before_work)?;
1195            payload_bytes = payload_bytes
1196                .checked_add(u64::try_from(durable.payload.len()).unwrap_or(u64::MAX))
1197                .ok_or_else(|| {
1198                    OperationFailure::before_work(AuthorityStoreError::ReplayRecordTooLarge {
1199                        observed: u64::MAX,
1200                        maximum: limit.payload_bytes,
1201                    })
1202                })?;
1203            if payload_bytes > limit.payload_bytes {
1204                if commits.is_empty() {
1205                    return Err(OperationFailure::before_work(
1206                        AuthorityStoreError::ReplayRecordTooLarge {
1207                            observed: payload_bytes,
1208                            maximum: limit.payload_bytes,
1209                        },
1210                    ));
1211                }
1212                break;
1213            }
1214            commits.push(durable);
1215        }
1216        let work = WorkCounters {
1217            authority_records_read: u64::try_from(commits.len()).unwrap_or(u64::MAX),
1218            authority_bytes_read: payload_bytes,
1219            backend_read_operations: 1,
1220            ..WorkCounters::default()
1221        };
1222        authority_success(commits, work, budget)
1223    }
1224
1225    async fn fence(
1226        &self,
1227        authority_id: AuthorityId,
1228        expected: Head,
1229        budget: WorkBudget,
1230        cancellation: &CancellationToken,
1231    ) -> AuthorityResult<FenceOutcome> {
1232        cancellation
1233            .check()
1234            .map_err(|_| OperationFailure::before_work(AuthorityStoreError::Cancelled))?;
1235        let snapshot = self
1236            .snapshot(authority_id)
1237            .await
1238            .map_err(OperationFailure::before_work)?;
1239        if snapshot.head != expected {
1240            return authority_success(
1241                FenceOutcome::Conflict {
1242                    actual: snapshot.head,
1243                },
1244                authority_read_work(2),
1245                budget,
1246            );
1247        }
1248        let next =
1249            expected.epoch.get().checked_add(1).ok_or_else(|| {
1250                OperationFailure::before_work(AuthorityStoreError::EpochExhausted)
1251            })?;
1252        let epoch = Epoch::new(next)
1253            .map_err(|_| OperationFailure::before_work(AuthorityStoreError::EpochExhausted))?;
1254        let records = records_path(authority_id).map_err(OperationFailure::before_work)?;
1255        let epochs = epochs_path(authority_id).map_err(OperationFailure::before_work)?;
1256        let mut fence_identity = Vec::with_capacity(64);
1257        fence_identity.extend_from_slice(&authority_id.into_bytes());
1258        fence_identity.extend_from_slice(&expected.epoch.get().to_le_bytes());
1259        fence_identity.extend_from_slice(&expected.sequence.get().to_le_bytes());
1260        fence_identity.extend_from_slice(expected.digest.as_bytes());
1261        let request = acyclic_stream::CommitRequest {
1262            conditions: vec![
1263                acyclic_stream::CommitCondition::Tail {
1264                    path: records,
1265                    expected: snapshot.record_tail,
1266                },
1267                acyclic_stream::CommitCondition::Tail {
1268                    path: epochs.clone(),
1269                    expected: snapshot.epoch_tail,
1270                },
1271            ],
1272            mutations: vec![acyclic_stream::CommitMutation::Append {
1273                path: epochs,
1274                records: vec![encode_epoch(EPOCH_DOMAIN, epoch)],
1275            }],
1276            idempotency_key: stream_key(b"fence", &fence_identity)
1277                .map_err(OperationFailure::before_work)?,
1278        };
1279        let work = authority_write_work(1, 0);
1280        admit_authority(work, budget)?;
1281        match self.provider.commit(request).await {
1282            Ok(acyclic_stream::CommitOutcome::Committed(_)) => authority_success(
1283                FenceOutcome::Advanced(Head { epoch, ..expected }),
1284                work,
1285                budget,
1286            ),
1287            Ok(acyclic_stream::CommitOutcome::Conflict(_)) => {
1288                let actual = self
1289                    .snapshot(authority_id)
1290                    .await
1291                    .map_err(|error| OperationFailure::new(error, work))?
1292                    .head;
1293                authority_success(FenceOutcome::Conflict { actual }, work, budget)
1294            }
1295            Err(error) => Err(OperationFailure::new(map_stream_error(error), work)),
1296        }
1297    }
1298
1299    async fn find_operation(
1300        &self,
1301        authority_id: AuthorityId,
1302        operation_id: OperationId,
1303        budget: WorkBudget,
1304        cancellation: &CancellationToken,
1305    ) -> AuthorityResult<Option<DurableCommit>> {
1306        cancellation
1307            .check()
1308            .map_err(|_| OperationFailure::before_work(AuthorityStoreError::Cancelled))?;
1309        let value = self
1310            .operation(authority_id, operation_id)
1311            .await
1312            .map_err(OperationFailure::before_work)?;
1313        let records_read = u64::from(value.is_some());
1314        authority_success(
1315            value,
1316            WorkCounters {
1317                authority_records_read: records_read,
1318                backend_read_operations: 1,
1319                ..WorkCounters::default()
1320            },
1321            budget,
1322        )
1323    }
1324}
1325
1326/// Immutable filesystem-object storage over one exact public Objects bucket.
1327///
1328/// The adapter contains no durable state. Object identity remains the canonical
1329/// filesystem digest; the Objects version is transport/storage evidence only.
1330#[derive(Clone)]
1331pub struct ProviderObjectStore<P> {
1332    provider: Arc<P>,
1333    bucket: wire::BucketRef,
1334}
1335
1336fn provider_put_request(bucket: &wire::BucketRef, write: &ObjectWrite) -> PutRequest {
1337    PutRequest {
1338        bucket: bucket.clone(),
1339        object_key: object_key(write.object_id),
1340        body: write.bytes.clone(),
1341        metadata: wire::ObjectMetadata {
1342            content_type: "application/vnd.acyclic.fs-object-v1".to_owned(),
1343            ..wire::ObjectMetadata::default()
1344        },
1345        condition: Some(Condition::IfAbsent),
1346        idempotency_key: Some(format!(
1347            "fs-object-{}",
1348            hex::encode(write.object_id.digest.as_bytes())
1349        )),
1350    }
1351}
1352
1353impl<P> ProviderObjectStore<P> {
1354    /// Binds an authenticated provider to the account's dedicated filesystem bucket.
1355    #[must_use]
1356    pub fn new(provider: Arc<P>, bucket: wire::BucketRef) -> Self {
1357        Self { provider, bucket }
1358    }
1359
1360    /// Returns the exact backing bucket identity used by this adapter.
1361    #[must_use]
1362    pub fn bucket(&self) -> &wire::BucketRef {
1363        &self.bucket
1364    }
1365
1366    /// Returns the exact public provider used by this stateless adapter.
1367    #[must_use]
1368    pub fn provider(&self) -> &Arc<P> {
1369        &self.provider
1370    }
1371}
1372
1373impl<P: ObjectsProvider> AsyncObjectStore for ProviderObjectStore<P> {
1374    async fn put(
1375        &self,
1376        object_id: ObjectId,
1377        bytes: Bytes,
1378        budget: WorkBudget,
1379        cancellation: &CancellationToken,
1380    ) -> ObjectResult<()> {
1381        cancellation
1382            .check()
1383            .map_err(|_| OperationFailure::before_work(ObjectStoreError::Cancelled))?;
1384        if object_digest(object_id.kind, &bytes) != object_id.digest {
1385            return Err(OperationFailure::before_work(
1386                ObjectStoreError::DigestMismatch,
1387            ));
1388        }
1389        let byte_count = u64::try_from(bytes.len()).unwrap_or(u64::MAX);
1390        let mut work = WorkCounters {
1391            backend_write_operations: 1,
1392            object_bytes_written: byte_count,
1393            bytes_hashed: byte_count,
1394            ..WorkCounters::default()
1395        };
1396        admit(work, budget)?;
1397        let request = PutRequest {
1398            bucket: self.bucket.clone(),
1399            object_key: object_key(object_id),
1400            body: bytes,
1401            metadata: wire::ObjectMetadata {
1402                content_type: "application/vnd.acyclic.fs-object-v1".to_owned(),
1403                ..wire::ObjectMetadata::default()
1404            },
1405            condition: Some(Condition::IfAbsent),
1406            idempotency_key: Some(format!(
1407                "fs-object-{}",
1408                hex::encode(object_id.digest.as_bytes())
1409            )),
1410        };
1411        match self.provider.put(request).await {
1412            Ok(version) if version.size == byte_count => success((), work, budget),
1413            Ok(_) => Err(OperationFailure::new(ObjectStoreError::Corrupt, work)),
1414            Err(ObjectsError::PreconditionFailed) => {
1415                work.backend_read_operations = work.backend_read_operations.saturating_add(1);
1416                let existing = self
1417                    .provider
1418                    .get(read_request(&self.bucket, object_id, byte_count))
1419                    .await
1420                    .map_err(|error| OperationFailure::new(map_objects_error(error), work))?;
1421                let existing_bytes = u64::try_from(existing.body.len()).unwrap_or(u64::MAX);
1422                work.object_bytes_read = work.object_bytes_read.saturating_add(existing_bytes);
1423                work.bytes_hashed = work.bytes_hashed.saturating_add(existing_bytes);
1424                if existing_bytes != byte_count
1425                    || object_digest(object_id.kind, &existing.body) != object_id.digest
1426                {
1427                    return Err(OperationFailure::new(ObjectStoreError::Corrupt, work));
1428                }
1429                success((), work, budget)
1430            }
1431            Err(error) => Err(OperationFailure::new(map_objects_error(error), work)),
1432        }
1433    }
1434
1435    async fn put_many(
1436        &self,
1437        writes: &[ObjectWrite],
1438        budget: WorkBudget,
1439        cancellation: &CancellationToken,
1440    ) -> ObjectResult<()> {
1441        cancellation
1442            .check()
1443            .map_err(|_| OperationFailure::before_work(ObjectStoreError::Cancelled))?;
1444        if writes.is_empty() {
1445            return Err(OperationFailure::before_work(ObjectStoreError::Rejected(
1446                "object write batch is empty".to_owned(),
1447            )));
1448        }
1449        let (requests, unique_writes, mut work) =
1450            prepare_provider_put_batch(&self.bucket, writes, budget, cancellation)?;
1451        work.backend_write_operations = 1;
1452        admit(work, budget)?;
1453        let results = self.provider.put_batch(requests).await;
1454        if results.len() != unique_writes.len() {
1455            return Err(OperationFailure::new(ObjectStoreError::Corrupt, work));
1456        }
1457        for (write_index, result) in unique_writes.into_iter().zip(results) {
1458            let write = writes
1459                .get(write_index)
1460                .ok_or_else(|| OperationFailure::new(ObjectStoreError::Corrupt, work))?;
1461            let byte_count = u64::try_from(write.bytes.len()).unwrap_or(u64::MAX);
1462            match result {
1463                Ok(version) if version.size == byte_count => {}
1464                Ok(_) => return Err(OperationFailure::new(ObjectStoreError::Corrupt, work)),
1465                Err(ObjectsError::PreconditionFailed) => {
1466                    cancellation
1467                        .check()
1468                        .map_err(|_| OperationFailure::new(ObjectStoreError::Cancelled, work))?;
1469                    work.backend_read_operations = work.backend_read_operations.saturating_add(1);
1470                    let existing = self
1471                        .provider
1472                        .get(read_request(&self.bucket, write.object_id, byte_count))
1473                        .await
1474                        .map_err(|error| OperationFailure::new(map_objects_error(error), work))?;
1475                    let existing_bytes = u64::try_from(existing.body.len()).unwrap_or(u64::MAX);
1476                    work.object_bytes_read = work.object_bytes_read.saturating_add(existing_bytes);
1477                    work.bytes_hashed = work.bytes_hashed.saturating_add(existing_bytes);
1478                    if existing_bytes != byte_count
1479                        || object_digest(write.object_id.kind, &existing.body)
1480                            != write.object_id.digest
1481                    {
1482                        return Err(OperationFailure::new(ObjectStoreError::Corrupt, work));
1483                    }
1484                }
1485                Err(error) => {
1486                    return Err(OperationFailure::new(map_objects_error(error), work));
1487                }
1488            }
1489        }
1490        success((), work, budget)
1491    }
1492
1493    async fn read(
1494        &self,
1495        object_id: ObjectId,
1496        maximum_bytes: u64,
1497        budget: WorkBudget,
1498        cancellation: &CancellationToken,
1499    ) -> ObjectResult<ObjectRead> {
1500        cancellation
1501            .check()
1502            .map_err(|_| OperationFailure::before_work(ObjectStoreError::Cancelled))?;
1503        let value = self
1504            .provider
1505            .get(read_request(&self.bucket, object_id, maximum_bytes))
1506            .await
1507            .map_err(|error| OperationFailure::before_work(map_objects_error(error)))?;
1508        let observed = u64::try_from(value.body.len()).unwrap_or(u64::MAX);
1509        let work = WorkCounters {
1510            object_probes: 1,
1511            backend_read_operations: 1,
1512            object_bytes_read: observed,
1513            bytes_hashed: observed,
1514            bytes_copied: observed,
1515            allocation_operations: u64::from(!value.body.is_empty()),
1516            peak_allocation_bytes: observed,
1517            ..WorkCounters::default()
1518        };
1519        if observed > maximum_bytes {
1520            return Err(OperationFailure::new(
1521                ObjectStoreError::TooLarge {
1522                    observed,
1523                    maximum: maximum_bytes,
1524                },
1525                work,
1526            ));
1527        }
1528        if object_digest(object_id.kind, &value.body) != object_id.digest {
1529            return Err(OperationFailure::new(ObjectStoreError::Corrupt, work));
1530        }
1531        success(
1532            ObjectRead {
1533                bytes: value.body,
1534                retention: ObjectReadRetention::Owned {
1535                    logical_bytes: observed,
1536                },
1537            },
1538            work,
1539            budget,
1540        )
1541    }
1542
1543    async fn read_many(
1544        &self,
1545        requests: &[ObjectReadRequest],
1546        budget: WorkBudget,
1547        cancellation: &CancellationToken,
1548    ) -> ObjectResult<Vec<ObjectRead>> {
1549        if requests.is_empty() {
1550            return Err(OperationFailure::before_work(ObjectStoreError::Rejected(
1551                "object read batch is empty".to_owned(),
1552            )));
1553        }
1554        cancellation
1555            .check()
1556            .map_err(|_| OperationFailure::before_work(ObjectStoreError::Cancelled))?;
1557        let mut values = Vec::new();
1558        values.try_reserve_exact(requests.len()).map_err(|_| {
1559            OperationFailure::before_work(ObjectStoreError::Rejected(
1560                "object batch allocation failed".to_owned(),
1561            ))
1562        })?;
1563        let provider_requests = requests
1564            .iter()
1565            .map(|request| read_request(&self.bucket, request.object_id, request.maximum_bytes))
1566            .collect();
1567        let results = self.provider.get_batch(provider_requests).await;
1568        if results.len() != requests.len() {
1569            return Err(OperationFailure::before_work(ObjectStoreError::Corrupt));
1570        }
1571        let vector_bytes = u64::try_from(
1572            requests
1573                .len()
1574                .saturating_mul(size_of::<ObjectRead>() + size_of::<GetRequest>()),
1575        )
1576        .unwrap_or(u64::MAX);
1577        let retained_bytes = results.iter().try_fold(0_u64, |total, result| {
1578            total.checked_add(result.as_ref().map_or(0, |value| {
1579                u64::try_from(value.body.len()).unwrap_or(u64::MAX)
1580            }))
1581        });
1582        let retained_bytes = retained_bytes.ok_or_else(|| {
1583            OperationFailure::before_work(ObjectStoreError::Work(WorkError::Overflow))
1584        })?;
1585        let mut work = WorkCounters {
1586            backend_read_operations: 1,
1587            allocation_operations: 2,
1588            peak_allocation_bytes: vector_bytes.checked_add(retained_bytes).ok_or_else(|| {
1589                OperationFailure::before_work(ObjectStoreError::Work(WorkError::Overflow))
1590            })?,
1591            ..WorkCounters::default()
1592        };
1593        work.verify(budget)
1594            .map_err(|error| OperationFailure::before_work(error.into()))?;
1595        for (request, result) in requests.iter().zip(results) {
1596            cancellation
1597                .check()
1598                .map_err(|_| OperationFailure::new(ObjectStoreError::Cancelled, work))?;
1599            let value =
1600                result.map_err(|error| OperationFailure::new(map_objects_error(error), work))?;
1601            let observed = u64::try_from(value.body.len()).unwrap_or(u64::MAX);
1602            work = work
1603                .checked_add(WorkCounters {
1604                    object_probes: 1,
1605                    object_bytes_read: observed,
1606                    bytes_hashed: observed,
1607                    bytes_copied: observed,
1608                    ..WorkCounters::default()
1609                })
1610                .map_err(|error| OperationFailure::new(error.into(), work))?;
1611            work.verify(budget)
1612                .map_err(|error| OperationFailure::new(error.into(), work))?;
1613            if observed > request.maximum_bytes {
1614                return Err(OperationFailure::new(
1615                    ObjectStoreError::TooLarge {
1616                        observed,
1617                        maximum: request.maximum_bytes,
1618                    },
1619                    work,
1620                ));
1621            }
1622            if object_digest(request.object_id.kind, &value.body) != request.object_id.digest {
1623                return Err(OperationFailure::new(ObjectStoreError::Corrupt, work));
1624            }
1625            values.push(ObjectRead {
1626                bytes: value.body,
1627                retention: ObjectReadRetention::Owned {
1628                    logical_bytes: observed,
1629                },
1630            });
1631        }
1632        success(values, work, budget)
1633    }
1634
1635    async fn contains(
1636        &self,
1637        object_id: ObjectId,
1638        budget: WorkBudget,
1639        cancellation: &CancellationToken,
1640    ) -> ObjectResult<bool> {
1641        cancellation
1642            .check()
1643            .map_err(|_| OperationFailure::before_work(ObjectStoreError::Cancelled))?;
1644        let work = WorkCounters {
1645            object_probes: 1,
1646            backend_read_operations: 1,
1647            ..WorkCounters::default()
1648        };
1649        admit(work, budget)?;
1650        match self
1651            .provider
1652            .get(read_request(
1653                &self.bucket,
1654                object_id,
1655                budget.object_bytes_read,
1656            ))
1657            .await
1658        {
1659            Ok(value) => {
1660                let valid = object_digest(object_id.kind, &value.body) == object_id.digest;
1661                if valid {
1662                    success(true, work, budget)
1663                } else {
1664                    Err(OperationFailure::new(ObjectStoreError::Corrupt, work))
1665                }
1666            }
1667            Err(ObjectsError::NotFound) => success(false, work, budget),
1668            Err(error) => Err(OperationFailure::new(map_objects_error(error), work)),
1669        }
1670    }
1671}
1672
1673pub(crate) fn object_key(object_id: ObjectId) -> String {
1674    format!(
1675        "fs/v1/{}/{}",
1676        object_id.kind.canonical_tag(),
1677        hex::encode(object_id.digest.as_bytes())
1678    )
1679}
1680
1681fn authority_prefix(authority_id: AuthorityId) -> String {
1682    format!("fs/authorities/{}", hex::encode(authority_id.into_bytes()))
1683}
1684
1685fn authority_path(
1686    authority_id: AuthorityId,
1687) -> Result<acyclic_stream::StreamPath, AuthorityStoreError> {
1688    acyclic_stream::StreamPath::new(authority_prefix(authority_id)).map_err(map_stream_error)
1689}
1690
1691fn records_path(
1692    authority_id: AuthorityId,
1693) -> Result<acyclic_stream::StreamPath, AuthorityStoreError> {
1694    acyclic_stream::StreamPath::new(format!("{}/records", authority_prefix(authority_id)))
1695        .map_err(map_stream_error)
1696}
1697
1698fn epochs_path(
1699    authority_id: AuthorityId,
1700) -> Result<acyclic_stream::StreamPath, AuthorityStoreError> {
1701    acyclic_stream::StreamPath::new(format!("{}/epochs", authority_prefix(authority_id)))
1702        .map_err(map_stream_error)
1703}
1704
1705fn publication_gate_path(
1706    authority_id: AuthorityId,
1707) -> Result<acyclic_stream::StreamPath, AuthorityStoreError> {
1708    acyclic_stream::StreamPath::new(format!(
1709        "{}/publication-gate",
1710        authority_prefix(authority_id)
1711    ))
1712    .map_err(map_stream_error)
1713}
1714
1715fn encode_active_reservation(operation_id: OperationId) -> Bytes {
1716    let mut value = Vec::with_capacity(PUBLICATION_GATE_ACTIVE.len() + 16);
1717    value.extend_from_slice(PUBLICATION_GATE_ACTIVE);
1718    value.extend_from_slice(&operation_id.into_bytes());
1719    Bytes::from(value)
1720}
1721
1722fn decode_publication_gate(value: &[u8]) -> Result<Option<OperationId>, AuthorityStoreError> {
1723    if value == PUBLICATION_GATE_FREE {
1724        return Ok(None);
1725    }
1726    let operation = value
1727        .strip_prefix(PUBLICATION_GATE_ACTIVE)
1728        .and_then(|bytes| <[u8; 16]>::try_from(bytes).ok())
1729        .ok_or_else(|| {
1730            AuthorityStoreError::Corrupt("invalid publication gate record".to_owned())
1731        })?;
1732    Ok(Some(OperationId::from_bytes(operation)))
1733}
1734
1735fn durable_from_operation_envelope(
1736    authority_id: AuthorityId,
1737    operation_id: OperationId,
1738    envelope: acyclic_stream::CommittedEnvelope,
1739) -> Result<DurableCommit, AuthorityStoreError> {
1740    let records = records_path(authority_id)?;
1741    let mut retained = None;
1742    for mutation in envelope.mutations {
1743        let acyclic_stream::CommittedMutation::Append(append) = mutation else {
1744            continue;
1745        };
1746        if append.path != records {
1747            continue;
1748        }
1749        let [record] = append.records.as_slice() else {
1750            return Err(AuthorityStoreError::Corrupt(
1751                "operation appended an invalid authority record count".to_owned(),
1752            ));
1753        };
1754        let durable = decode_durable(authority_id, &record.value)?;
1755        if durable.operation_id != operation_id || retained.replace(durable).is_some() {
1756            return Err(AuthorityStoreError::Corrupt(
1757                "operation envelope does not bind its authority operation".to_owned(),
1758            ));
1759        }
1760    }
1761    retained.ok_or_else(|| {
1762        AuthorityStoreError::Corrupt("operation envelope omits its authority record".to_owned())
1763    })
1764}
1765
1766fn lineage_path(
1767    authority_id: AuthorityId,
1768) -> Result<acyclic_stream::StreamPath, AuthorityStoreError> {
1769    acyclic_stream::StreamPath::new(format!("{}/lineage", authority_prefix(authority_id)))
1770        .map_err(map_stream_error)
1771}
1772
1773fn generation_path(
1774    authority_id: AuthorityId,
1775    generation: GenerationId,
1776) -> Result<acyclic_stream::StreamPath, AuthorityStoreError> {
1777    acyclic_stream::StreamPath::new(format!(
1778        "{}/generations/{}",
1779        authority_prefix(authority_id),
1780        hex::encode(generation.digest().as_bytes())
1781    ))
1782    .map_err(map_stream_error)
1783}
1784
1785fn stream_key(
1786    domain: &[u8],
1787    identity: &[u8],
1788) -> Result<acyclic_stream::IdempotencyKey, AuthorityStoreError> {
1789    let mut value = Vec::with_capacity(
1790        domain
1791            .len()
1792            .saturating_add(identity.len())
1793            .saturating_add(1),
1794    );
1795    value.extend_from_slice(domain);
1796    value.push(0);
1797    value.extend_from_slice(identity);
1798    acyclic_stream::IdempotencyKey::new(Bytes::from(value)).map_err(map_stream_error)
1799}
1800
1801/// Destination paths materialized by one authority-fork commit.
1802struct ForkDestinationPaths {
1803    root: acyclic_stream::StreamPath,
1804    records: acyclic_stream::StreamPath,
1805    epochs: acyclic_stream::StreamPath,
1806    locator: acyclic_stream::StreamPath,
1807    lineage: acyclic_stream::StreamPath,
1808    gate: acyclic_stream::StreamPath,
1809}
1810
1811/// Builds the one atomic commit that materializes a forked authority's root,
1812/// records, epochs, generation locator, and lineage fork in a single request.
1813fn fork_generation_commit_request(
1814    destination: ForkDestinationPaths,
1815    source_lineage: acyclic_stream::StreamPath,
1816    forked_at: u64,
1817    operation_id: OperationId,
1818) -> Result<acyclic_stream::CommitRequest, OperationFailure<AuthorityStoreError>> {
1819    Ok(acyclic_stream::CommitRequest {
1820        conditions: vec![
1821            acyclic_stream::CommitCondition::Absent {
1822                path: destination.root.clone(),
1823            },
1824            acyclic_stream::CommitCondition::Absent {
1825                path: destination.records.clone(),
1826            },
1827            acyclic_stream::CommitCondition::Absent {
1828                path: destination.epochs.clone(),
1829            },
1830            acyclic_stream::CommitCondition::Absent {
1831                path: destination.locator.clone(),
1832            },
1833            acyclic_stream::CommitCondition::Absent {
1834                path: destination.lineage.clone(),
1835            },
1836            acyclic_stream::CommitCondition::Absent {
1837                path: destination.gate.clone(),
1838            },
1839        ],
1840        mutations: vec![
1841            acyclic_stream::CommitMutation::Append {
1842                path: destination.root,
1843                records: vec![Bytes::from_static(AUTHORITY_MARKER)],
1844            },
1845            acyclic_stream::CommitMutation::Fork {
1846                source: source_lineage,
1847                destination: destination.lineage,
1848                at_tail: forked_at,
1849            },
1850            acyclic_stream::CommitMutation::Append {
1851                path: destination.records,
1852                records: vec![encode_epoch(GENESIS_DOMAIN, Epoch::GENESIS)],
1853            },
1854            acyclic_stream::CommitMutation::Append {
1855                path: destination.epochs,
1856                records: vec![encode_epoch(EPOCH_DOMAIN, Epoch::GENESIS)],
1857            },
1858            acyclic_stream::CommitMutation::Append {
1859                path: destination.locator,
1860                records: vec![encode_lineage_tail(forked_at)],
1861            },
1862            acyclic_stream::CommitMutation::Append {
1863                path: destination.gate,
1864                records: vec![Bytes::from_static(PUBLICATION_GATE_FREE)],
1865            },
1866        ],
1867        idempotency_key: stream_key(b"fork-authority", &operation_id.into_bytes())
1868            .map_err(OperationFailure::before_work)?,
1869    })
1870}
1871
1872fn operation_key(
1873    authority_id: AuthorityId,
1874    operation_id: OperationId,
1875) -> Result<acyclic_stream::IdempotencyKey, AuthorityStoreError> {
1876    let mut identity = [0; 32];
1877    identity[..16].copy_from_slice(&authority_id.into_bytes());
1878    identity[16..].copy_from_slice(&operation_id.into_bytes());
1879    stream_key(b"operation", &identity)
1880}
1881
1882fn publication_reservation_key(
1883    domain: &[u8],
1884    authority_id: AuthorityId,
1885    operation_id: OperationId,
1886    gate_tail: u64,
1887) -> Result<acyclic_stream::IdempotencyKey, AuthorityStoreError> {
1888    let mut identity = Vec::with_capacity(40);
1889    identity.extend_from_slice(&authority_id.into_bytes());
1890    identity.extend_from_slice(&operation_id.into_bytes());
1891    identity.extend_from_slice(&gate_tail.to_le_bytes());
1892    stream_key(domain, &identity)
1893}
1894
1895fn encode_epoch(domain: &[u8], epoch: Epoch) -> Bytes {
1896    let mut value = Vec::with_capacity(domain.len().saturating_add(8));
1897    value.extend_from_slice(domain);
1898    value.extend_from_slice(&epoch.get().to_le_bytes());
1899    Bytes::from(value)
1900}
1901
1902fn generation_from_payload(
1903    authority_id: AuthorityId,
1904    sequence: Sequence,
1905    payload: &[u8],
1906) -> Result<Option<GenerationId>, AuthorityStoreError> {
1907    if let Ok(created) = decode_volume_created(payload, STREAM_RECORD_LIMIT) {
1908        if sequence != Sequence::new(1) || volume_authority_id(created.volume_id) != authority_id {
1909            return Err(AuthorityStoreError::Corrupt(
1910                "volume creation identity or sequence does not match its authority".to_owned(),
1911            ));
1912        }
1913        return Ok(Some(GenerationId::new(
1914            created.initial_generation_root.digest,
1915        )));
1916    }
1917    if let Ok(published) = decode_published_generation(payload, STREAM_RECORD_LIMIT) {
1918        if sequence == Sequence::new(1) || volume_authority_id(published.volume_id) != authority_id
1919        {
1920            return Err(AuthorityStoreError::Corrupt(
1921                "generation publication identity or sequence does not match its authority"
1922                    .to_owned(),
1923            ));
1924        }
1925        return Ok(Some(GenerationId::new(published.generation_root.digest)));
1926    }
1927    Ok(None)
1928}
1929
1930fn encode_lineage_generation(generation: GenerationId) -> Bytes {
1931    let mut value = Vec::with_capacity(LINEAGE_DOMAIN.len().saturating_add(32));
1932    value.extend_from_slice(LINEAGE_DOMAIN);
1933    value.extend_from_slice(generation.digest().as_bytes());
1934    Bytes::from(value)
1935}
1936
1937fn decode_lineage_generation(encoded: &[u8]) -> Result<GenerationId, AuthorityStoreError> {
1938    if encoded.len() != LINEAGE_DOMAIN.len().saturating_add(32)
1939        || !encoded.starts_with(LINEAGE_DOMAIN)
1940    {
1941        return Err(AuthorityStoreError::Corrupt(
1942            "invalid Stream generation-lineage record".to_owned(),
1943        ));
1944    }
1945    let digest = encoded
1946        .get(LINEAGE_DOMAIN.len()..)
1947        .ok_or_else(|| {
1948            AuthorityStoreError::Corrupt("truncated Stream generation-lineage record".to_owned())
1949        })?
1950        .try_into()
1951        .map(Digest::from_bytes)
1952        .map_err(|_| {
1953            AuthorityStoreError::Corrupt("truncated Stream generation-lineage record".to_owned())
1954        })?;
1955    Ok(GenerationId::new(digest))
1956}
1957
1958fn encode_lineage_tail(tail: u64) -> Bytes {
1959    let mut value = Vec::with_capacity(LINEAGE_TAIL_DOMAIN.len().saturating_add(8));
1960    value.extend_from_slice(LINEAGE_TAIL_DOMAIN);
1961    value.extend_from_slice(&tail.to_le_bytes());
1962    Bytes::from(value)
1963}
1964
1965fn decode_lineage_tail(encoded: &[u8]) -> Result<u64, AuthorityStoreError> {
1966    if encoded.len() != LINEAGE_TAIL_DOMAIN.len().saturating_add(8)
1967        || !encoded.starts_with(LINEAGE_TAIL_DOMAIN)
1968    {
1969        return Err(AuthorityStoreError::Corrupt(
1970            "invalid Stream generation locator".to_owned(),
1971        ));
1972    }
1973    let bytes: [u8; 8] = encoded
1974        .get(LINEAGE_TAIL_DOMAIN.len()..)
1975        .ok_or_else(|| AuthorityStoreError::Corrupt("truncated generation locator".to_owned()))?
1976        .try_into()
1977        .map_err(|_| AuthorityStoreError::Corrupt("truncated generation locator".to_owned()))?;
1978    Ok(u64::from_le_bytes(bytes))
1979}
1980
1981fn decode_epoch(encoded: &[u8], domain: &[u8]) -> Result<Epoch, AuthorityStoreError> {
1982    if encoded.len() != domain.len().saturating_add(8) || !encoded.starts_with(domain) {
1983        return Err(AuthorityStoreError::Corrupt(
1984            "invalid Stream epoch record".to_owned(),
1985        ));
1986    }
1987    let bytes: [u8; 8] = encoded
1988        .get(domain.len()..)
1989        .ok_or_else(|| AuthorityStoreError::Corrupt("truncated Stream epoch record".to_owned()))?
1990        .try_into()
1991        .map_err(|_| AuthorityStoreError::Corrupt("truncated Stream epoch record".to_owned()))?;
1992    Epoch::new(u64::from_le_bytes(bytes))
1993        .map_err(|error| AuthorityStoreError::Corrupt(error.to_string()))
1994}
1995
1996async fn read_one<P: acyclic_stream::StreamProvider>(
1997    provider: &P,
1998    path: acyclic_stream::StreamPath,
1999    from: u64,
2000) -> Result<acyclic_stream::Record, AuthorityStoreError> {
2001    let mut records = provider
2002        .read(acyclic_stream::ReadRequest {
2003            path,
2004            from,
2005            limit: 1,
2006        })
2007        .await
2008        .map_err(map_stream_error)?;
2009    let record = records
2010        .next()
2011        .await
2012        .ok_or_else(|| {
2013            AuthorityStoreError::Corrupt("Stream returned an empty exact read".to_owned())
2014        })?
2015        .map_err(map_stream_error)?;
2016    if record.sequence != from {
2017        return Err(AuthorityStoreError::Corrupt(
2018            "Stream returned a non-contiguous record".to_owned(),
2019        ));
2020    }
2021    Ok(record)
2022}
2023
2024fn decode_durable(
2025    authority_id: AuthorityId,
2026    encoded: &[u8],
2027) -> Result<DurableCommit, AuthorityStoreError> {
2028    let durable = StreamsDurableRecord::decode(encoded, STREAM_RECORD_LIMIT)
2029        .map_err(|error| AuthorityStoreError::Corrupt(error.to_string()))?
2030        .0;
2031    let expected = authority_commit_digest(
2032        authority_id,
2033        durable.epoch,
2034        durable.sequence,
2035        durable.operation_id,
2036        durable.fingerprint,
2037        durable.previous_digest,
2038        &durable.payload,
2039    );
2040    if expected != durable.digest {
2041        return Err(AuthorityStoreError::Corrupt(
2042            "Stream authority record digest mismatch".to_owned(),
2043        ));
2044    }
2045    Ok(durable)
2046}
2047
2048fn map_stream_error(error: acyclic_stream::StreamError) -> AuthorityStoreError {
2049    match error {
2050        acyclic_stream::StreamError::NotFound => AuthorityStoreError::Missing,
2051        acyclic_stream::StreamError::Capacity => {
2052            AuthorityStoreError::Rejected("Stream capacity exhausted".to_owned())
2053        }
2054        other => AuthorityStoreError::Rejected(other.to_string()),
2055    }
2056}
2057
2058fn authority_read_work(records: u64) -> WorkCounters {
2059    WorkCounters {
2060        authority_records_read: records,
2061        backend_read_operations: records,
2062        ..WorkCounters::default()
2063    }
2064}
2065
2066fn authority_write_work(records: u64, payload_bytes: u64) -> WorkCounters {
2067    WorkCounters {
2068        authority_records_appended: records,
2069        authority_bytes_written: payload_bytes,
2070        backend_write_operations: 1,
2071        durability_operations: 1,
2072        ..WorkCounters::default()
2073    }
2074}
2075
2076fn admit_authority(
2077    work: WorkCounters,
2078    budget: WorkBudget,
2079) -> Result<(), OperationFailure<AuthorityStoreError>> {
2080    work.verify(budget)
2081        .map_err(|error| OperationFailure::new(error.into(), work))
2082}
2083
2084fn authority_success<T>(value: T, work: WorkCounters, budget: WorkBudget) -> AuthorityResult<T> {
2085    admit_authority(work, budget)?;
2086    Ok(AuthorityReceipt { value, work })
2087}
2088
2089fn read_request(bucket: &wire::BucketRef, object_id: ObjectId, maximum_bytes: u64) -> GetRequest {
2090    GetRequest {
2091        target: ReadTarget::Bucket(bucket.clone()),
2092        object_key: object_key(object_id),
2093        version_id: None,
2094        range: None,
2095        if_match: None,
2096        if_none_match: None,
2097        maximum_bytes,
2098    }
2099}
2100
2101fn prepare_provider_put_batch(
2102    bucket: &wire::BucketRef,
2103    writes: &[ObjectWrite],
2104    budget: WorkBudget,
2105    cancellation: &CancellationToken,
2106) -> Result<(Vec<PutRequest>, Vec<usize>, WorkCounters), OperationFailure<ObjectStoreError>> {
2107    let minimum_bytes =
2108        u64::try_from(writes.len().saturating_mul(size_of::<PutRequest>())).unwrap_or(u64::MAX);
2109    let prospective = WorkCounters {
2110        allocation_operations: 1,
2111        peak_allocation_bytes: minimum_bytes,
2112        ..WorkCounters::default()
2113    };
2114    prospective
2115        .verify(budget)
2116        .map_err(|error| OperationFailure::before_work(error.into()))?;
2117    let mut requests = Vec::new();
2118    requests.try_reserve_exact(writes.len()).map_err(|_| {
2119        OperationFailure::before_work(ObjectStoreError::Rejected(
2120            "object batch allocation failed".to_owned(),
2121        ))
2122    })?;
2123    let mut work = WorkCounters {
2124        allocation_operations: 1,
2125        peak_allocation_bytes: u64::try_from(
2126            requests.capacity().saturating_mul(size_of::<PutRequest>()),
2127        )
2128        .unwrap_or(u64::MAX),
2129        ..WorkCounters::default()
2130    };
2131    admit(work, budget)?;
2132    let mut unique: BTreeMap<ObjectId, usize> = BTreeMap::new();
2133    let mut unique_writes = Vec::new();
2134    for (write_index, write) in writes.iter().enumerate() {
2135        cancellation
2136            .check()
2137            .map_err(|_| OperationFailure::new(ObjectStoreError::Cancelled, work))?;
2138        if object_digest(write.object_id.kind, &write.bytes) != write.object_id.digest {
2139            return Err(OperationFailure::new(
2140                ObjectStoreError::DigestMismatch,
2141                work,
2142            ));
2143        }
2144        let byte_count = u64::try_from(write.bytes.len()).unwrap_or(u64::MAX);
2145        work.object_bytes_written = work
2146            .object_bytes_written
2147            .checked_add(byte_count)
2148            .ok_or_else(|| {
2149                OperationFailure::new(
2150                    ObjectStoreError::Rejected("object batch byte count overflowed".to_owned()),
2151                    work,
2152                )
2153            })?;
2154        work.bytes_hashed = work.bytes_hashed.checked_add(byte_count).ok_or_else(|| {
2155            OperationFailure::new(
2156                ObjectStoreError::Rejected("object batch byte count overflowed".to_owned()),
2157                work,
2158            )
2159        })?;
2160        admit(work, budget)?;
2161        if let Some(existing_index) = unique.get(&write.object_id) {
2162            if writes
2163                .get(*existing_index)
2164                .is_none_or(|existing| existing.bytes != write.bytes)
2165            {
2166                return Err(OperationFailure::new(
2167                    ObjectStoreError::DigestMismatch,
2168                    work,
2169                ));
2170            }
2171        } else {
2172            unique.insert(write.object_id, write_index);
2173            unique_writes.push(write_index);
2174            requests.push(provider_put_request(bucket, write));
2175        }
2176    }
2177    Ok((requests, unique_writes, work))
2178}
2179
2180fn map_objects_error(error: ObjectsError) -> ObjectStoreError {
2181    match error {
2182        ObjectsError::NotFound => ObjectStoreError::Missing,
2183        ObjectsError::Capacity => {
2184            ObjectStoreError::Rejected("Objects capacity exhausted".to_owned())
2185        }
2186        other => ObjectStoreError::Rejected(other.to_string()),
2187    }
2188}
2189
2190fn admit(work: WorkCounters, budget: WorkBudget) -> Result<(), OperationFailure<ObjectStoreError>> {
2191    work.verify(budget)
2192        .map_err(|error| OperationFailure::new(error.into(), work))
2193}
2194
2195fn success<T>(value: T, work: WorkCounters, budget: WorkBudget) -> ObjectResult<T> {
2196    admit(work, budget)?;
2197    Ok(ObjectReceipt { value, work })
2198}
2199
2200#[cfg(test)]
2201#[allow(clippy::expect_used, clippy::panic)]
2202mod tests {
2203    use super::*;
2204    use crate::kernel::{VolumeCreated, encode_publication_payload, encode_volume_created};
2205    use crate::model::{Lifecycle, VolumeConfig};
2206    use crate::storage::ObjectKind;
2207
2208    #[tokio::test]
2209    async fn existing_authority_without_publication_gate_is_upgraded() {
2210        let stream = Arc::new(acyclic_stream::MemoryStream::default());
2211        let authority_id = AuthorityId::from_bytes([0x31; 16]);
2212        let root = authority_path(authority_id).expect("root path");
2213        let records = records_path(authority_id).expect("records path");
2214        let epochs = epochs_path(authority_id).expect("epochs path");
2215        acyclic_stream::StreamProvider::commit(
2216            stream.as_ref(),
2217            acyclic_stream::CommitRequest {
2218                conditions: vec![
2219                    acyclic_stream::CommitCondition::Absent { path: root.clone() },
2220                    acyclic_stream::CommitCondition::Absent {
2221                        path: records.clone(),
2222                    },
2223                    acyclic_stream::CommitCondition::Absent {
2224                        path: epochs.clone(),
2225                    },
2226                ],
2227                mutations: vec![
2228                    acyclic_stream::CommitMutation::Append {
2229                        path: root,
2230                        records: vec![Bytes::from_static(AUTHORITY_MARKER)],
2231                    },
2232                    acyclic_stream::CommitMutation::Append {
2233                        path: records,
2234                        records: vec![encode_epoch(GENESIS_DOMAIN, Epoch::GENESIS)],
2235                    },
2236                    acyclic_stream::CommitMutation::Append {
2237                        path: epochs,
2238                        records: vec![encode_epoch(EPOCH_DOMAIN, Epoch::GENESIS)],
2239                    },
2240                ],
2241                idempotency_key: stream_key(b"legacy-authority", &authority_id.into_bytes())
2242                    .expect("legacy idempotency key"),
2243            },
2244        )
2245        .await
2246        .expect("seed legacy authority");
2247
2248        let authority = StreamAuthorityStore::new(Arc::clone(&stream));
2249        let cancellation = CancellationToken::new();
2250        let denied = authority
2251            .create_authority(
2252                authority_id,
2253                Epoch::GENESIS,
2254                WorkBudget {
2255                    authority_records_read: 3,
2256                    backend_read_operations: 3,
2257                    ..WorkBudget::UNBOUNDED
2258                },
2259                &cancellation,
2260            )
2261            .await
2262            .expect_err("write budget must reject migration before mutation");
2263        assert!(
2264            matches!(denied.error, AuthorityStoreError::Work(_)),
2265            "unexpected migration denial: {:?}",
2266            denied.error
2267        );
2268        assert!(matches!(
2269            acyclic_stream::StreamProvider::tail(
2270                stream.as_ref(),
2271                publication_gate_path(authority_id).expect("gate path"),
2272            )
2273            .await,
2274            Err(acyclic_stream::StreamError::NotFound)
2275        ));
2276
2277        let opened = authority
2278            .create_authority(
2279                authority_id,
2280                Epoch::GENESIS,
2281                WorkBudget::UNBOUNDED,
2282                &cancellation,
2283            )
2284            .await
2285            .expect("upgrade legacy authority");
2286        let CreateAuthorityOutcome::Existing(head) = opened.value else {
2287            panic!("legacy authority must reopen as existing");
2288        };
2289        assert_eq!(opened.work.authority_records_appended, 1);
2290
2291        let committed = authority
2292            .compare_and_append_guarded(
2293                crate::GuardedAppend {
2294                    authority_id,
2295                    epoch: head.epoch,
2296                    expected: head,
2297                    commit: ProposedCommit {
2298                        operation_id: OperationId::from_bytes([0x32; 16]),
2299                        fingerprint: Digest::from_bytes([0x33; 32]),
2300                        payload: Bytes::from_static(b"legacy authority write"),
2301                    },
2302                    permit: PublicationPermit::Unrestricted,
2303                },
2304                WorkBudget::UNBOUNDED,
2305                &cancellation,
2306            )
2307            .await
2308            .expect("append after upgrade")
2309            .value;
2310        let FsAppendOutcome::Committed(committed) = committed else {
2311            panic!("unexpected append outcome: {committed:?}");
2312        };
2313        let committed_head = Head {
2314            epoch: committed.epoch,
2315            sequence: committed.sequence,
2316            digest: committed.digest,
2317        };
2318        let reservation = authority
2319            .reserve_publication(
2320                authority_id,
2321                committed_head,
2322                OperationId::from_bytes([0x34; 16]),
2323                WorkBudget::UNBOUNDED,
2324                &cancellation,
2325            )
2326            .await
2327            .expect("reserve upgraded authority")
2328            .value;
2329        let ReservationOutcome::Reserved(reservation) = reservation else {
2330            panic!("unexpected reservation outcome: {reservation:?}");
2331        };
2332        authority
2333            .release_publication(reservation, WorkBudget::UNBOUNDED, &cancellation)
2334            .await
2335            .expect("release upgraded authority");
2336    }
2337
2338    #[tokio::test]
2339    async fn existing_authority_rejects_a_corrupt_publication_gate() {
2340        let stream = Arc::new(acyclic_stream::MemoryStream::default());
2341        let authority = StreamAuthorityStore::new(Arc::clone(&stream));
2342        let authority_id = AuthorityId::from_bytes([0x35; 16]);
2343        let cancellation = CancellationToken::new();
2344        authority
2345            .create_authority(
2346                authority_id,
2347                Epoch::GENESIS,
2348                WorkBudget::UNBOUNDED,
2349                &cancellation,
2350            )
2351            .await
2352            .expect("create authority");
2353        let gate = publication_gate_path(authority_id).expect("gate path");
2354        acyclic_stream::StreamProvider::commit(
2355            stream.as_ref(),
2356            acyclic_stream::CommitRequest {
2357                conditions: vec![acyclic_stream::CommitCondition::Tail {
2358                    path: gate.clone(),
2359                    expected: 1,
2360                }],
2361                mutations: vec![acyclic_stream::CommitMutation::Append {
2362                    path: gate,
2363                    records: vec![Bytes::from_static(b"invalid-publication-gate")],
2364                }],
2365                idempotency_key: stream_key(
2366                    b"corrupt-publication-gate",
2367                    &authority_id.into_bytes(),
2368                )
2369                .expect("corruption key"),
2370            },
2371        )
2372        .await
2373        .expect("append invalid gate record");
2374
2375        let error = authority
2376            .create_authority(
2377                authority_id,
2378                Epoch::GENESIS,
2379                WorkBudget::UNBOUNDED,
2380                &cancellation,
2381            )
2382            .await
2383            .expect_err("corrupt gate must fail authority reopen");
2384        assert!(matches!(error.error, AuthorityStoreError::Corrupt(_)));
2385    }
2386
2387    #[tokio::test]
2388    async fn publication_reservation_excludes_unreserved_writers() {
2389        let stream = Arc::new(acyclic_stream::MemoryStream::default());
2390        let authority = StreamAuthorityStore::new(Arc::clone(&stream));
2391        let authority_id = AuthorityId::from_bytes([0x41; 16]);
2392        let cancellation = CancellationToken::new();
2393        authority
2394            .create_authority(
2395                authority_id,
2396                Epoch::GENESIS,
2397                WorkBudget::UNBOUNDED,
2398                &cancellation,
2399            )
2400            .await
2401            .expect("create authority");
2402        let head = authority
2403            .head(authority_id, WorkBudget::UNBOUNDED, &cancellation)
2404            .await
2405            .expect("head")
2406            .value;
2407        let operation_id = OperationId::from_bytes([0x42; 16]);
2408        let reservation = match authority
2409            .reserve_publication(
2410                authority_id,
2411                head,
2412                operation_id,
2413                WorkBudget::UNBOUNDED,
2414                &cancellation,
2415            )
2416            .await
2417            .expect("reserve")
2418            .value
2419        {
2420            ReservationOutcome::Reserved(reservation) => reservation,
2421            outcome => panic!("unexpected reservation outcome: {outcome:?}"),
2422        };
2423        let proposed = ProposedCommit {
2424            operation_id: OperationId::from_bytes([0x43; 16]),
2425            fingerprint: Digest::from_bytes([0x44; 32]),
2426            payload: Bytes::from_static(b"reserved write"),
2427        };
2428        let ordinary = authority
2429            .compare_and_append_guarded(
2430                crate::GuardedAppend {
2431                    authority_id,
2432                    epoch: head.epoch,
2433                    expected: head,
2434                    commit: proposed.clone(),
2435                    permit: PublicationPermit::Unrestricted,
2436                },
2437                WorkBudget::UNBOUNDED,
2438                &cancellation,
2439            )
2440            .await
2441            .expect("ordinary writer is rejected")
2442            .value;
2443        assert!(matches!(ordinary, FsAppendOutcome::Fenced { .. }));
2444        let reserved = authority
2445            .compare_and_append_guarded(
2446                crate::GuardedAppend {
2447                    authority_id,
2448                    epoch: head.epoch,
2449                    expected: head,
2450                    commit: proposed,
2451                    permit: PublicationPermit::Reservation {
2452                        operation_id: operation_id.into_bytes(),
2453                        gate_tail: reservation.gate_tail,
2454                        expected: reservation.expected,
2455                    },
2456                },
2457                WorkBudget::UNBOUNDED,
2458                &cancellation,
2459            )
2460            .await
2461            .expect("reserved writer")
2462            .value;
2463        assert!(matches!(reserved, FsAppendOutcome::Committed(_)));
2464        let denied_release = authority
2465            .release_publication(reservation, WorkBudget::default(), &cancellation)
2466            .await
2467            .expect_err("release must charge its idempotency read before doing work");
2468        assert!(matches!(denied_release.error, AuthorityStoreError::Work(_)));
2469        authority
2470            .release_publication(reservation, WorkBudget::UNBOUNDED, &cancellation)
2471            .await
2472            .expect("release");
2473        authority
2474            .release_publication(reservation, WorkBudget::UNBOUNDED, &cancellation)
2475            .await
2476            .expect("exact release retry is idempotent");
2477        let current = authority
2478            .head(authority_id, WorkBudget::UNBOUNDED, &cancellation)
2479            .await
2480            .expect("head after publication")
2481            .value;
2482        let reacquired = match authority
2483            .reserve_publication(
2484                authority_id,
2485                current,
2486                operation_id,
2487                WorkBudget::UNBOUNDED,
2488                &cancellation,
2489            )
2490            .await
2491            .expect("reacquire")
2492            .value
2493        {
2494            ReservationOutcome::Reserved(reservation) => reservation,
2495            outcome => panic!("unexpected reacquisition outcome: {outcome:?}"),
2496        };
2497        let forged = PublicationReservation {
2498            operation_id: OperationId::from_bytes([0x99; 16]),
2499            ..reacquired
2500        };
2501        assert!(
2502            authority
2503                .release_publication(forged, WorkBudget::UNBOUNDED, &cancellation)
2504                .await
2505                .is_err()
2506        );
2507        let gate = publication_gate_path(authority_id).expect("gate path");
2508        let advanced = acyclic_stream::StreamProvider::commit(
2509            stream.as_ref(),
2510            acyclic_stream::CommitRequest {
2511                conditions: vec![acyclic_stream::CommitCondition::Tail {
2512                    path: gate.clone(),
2513                    expected: reacquired.gate_tail,
2514                }],
2515                mutations: vec![acyclic_stream::CommitMutation::Append {
2516                    path: gate.clone(),
2517                    records: vec![encode_active_reservation(reacquired.operation_id)],
2518                }],
2519                idempotency_key: stream_key(b"advance-active-gate", &authority_id.into_bytes())
2520                    .expect("advance key"),
2521            },
2522        )
2523        .await
2524        .expect("advance active gate");
2525        assert!(matches!(
2526            advanced,
2527            acyclic_stream::CommitOutcome::Committed(_)
2528        ));
2529
2530        let release_key = publication_reservation_key(
2531            b"release-publication",
2532            reacquired.authority_id,
2533            reacquired.operation_id,
2534            reacquired.gate_tail,
2535        )
2536        .expect("release key");
2537        let retained_conflict = acyclic_stream::StreamProvider::commit(
2538            stream.as_ref(),
2539            acyclic_stream::CommitRequest {
2540                conditions: vec![acyclic_stream::CommitCondition::Tail {
2541                    path: gate.clone(),
2542                    expected: reacquired.gate_tail,
2543                }],
2544                mutations: vec![acyclic_stream::CommitMutation::Append {
2545                    path: gate,
2546                    records: vec![Bytes::from_static(PUBLICATION_GATE_FREE)],
2547                }],
2548                idempotency_key: release_key,
2549            },
2550        )
2551        .await
2552        .expect("retain release conflict");
2553        assert!(matches!(
2554            retained_conflict,
2555            acyclic_stream::CommitOutcome::Conflict(_)
2556        ));
2557        authority
2558            .release_publication(reacquired, WorkBudget::UNBOUNDED, &cancellation)
2559            .await
2560            .expect_err("retained release conflict must not become success");
2561        let (_, active) = authority
2562            .publication_gate(authority_id)
2563            .await
2564            .expect("read active gate");
2565        assert_eq!(active, Some(reacquired.operation_id));
2566    }
2567
2568    #[tokio::test]
2569    async fn provider_object_batch_reads_once_and_preserves_order()
2570    -> Result<(), Box<dyn std::error::Error>> {
2571        let (provider, bucket) = acyclic_objects::MemoryObjects::with_default_bucket();
2572        let store = ProviderObjectStore::new(Arc::new(provider), bucket);
2573        let first_bytes = Bytes::from_static(b"first");
2574        let second_bytes = Bytes::from_static(b"second");
2575        let first = ObjectId {
2576            kind: ObjectKind::BlobChunk,
2577            digest: object_digest(ObjectKind::BlobChunk, &first_bytes),
2578        };
2579        let second = ObjectId {
2580            kind: ObjectKind::BlobChunk,
2581            digest: object_digest(ObjectKind::BlobChunk, &second_bytes),
2582        };
2583        let cancellation = CancellationToken::new();
2584        store
2585            .put(
2586                first,
2587                first_bytes.clone(),
2588                WorkBudget::UNBOUNDED,
2589                &cancellation,
2590            )
2591            .await?;
2592        store
2593            .put(
2594                second,
2595                second_bytes.clone(),
2596                WorkBudget::UNBOUNDED,
2597                &cancellation,
2598            )
2599            .await?;
2600        let receipt = store
2601            .read_many(
2602                &[
2603                    ObjectReadRequest {
2604                        object_id: second,
2605                        maximum_bytes: 6,
2606                    },
2607                    ObjectReadRequest {
2608                        object_id: first,
2609                        maximum_bytes: 5,
2610                    },
2611                ],
2612                WorkBudget::UNBOUNDED,
2613                &cancellation,
2614            )
2615            .await?;
2616        assert_eq!(
2617            receipt.value.first().map(|value| &value.bytes),
2618            Some(&second_bytes)
2619        );
2620        assert_eq!(
2621            receipt.value.get(1).map(|value| &value.bytes),
2622            Some(&first_bytes)
2623        );
2624        assert_eq!(receipt.work.backend_read_operations, 1);
2625        assert_eq!(receipt.work.object_probes, 2);
2626        Ok(())
2627    }
2628
2629    #[tokio::test]
2630    async fn provider_object_batch_counts_one_backend_write()
2631    -> Result<(), Box<dyn std::error::Error>> {
2632        let (provider, bucket) = acyclic_objects::MemoryObjects::with_default_bucket();
2633        let store = ProviderObjectStore::new(Arc::new(provider), bucket);
2634        let first_bytes = Bytes::from_static(b"first");
2635        let second_bytes = Bytes::from_static(b"second");
2636        let writes = [
2637            ObjectWrite {
2638                object_id: ObjectId {
2639                    kind: ObjectKind::BlobChunk,
2640                    digest: object_digest(ObjectKind::BlobChunk, &first_bytes),
2641                },
2642                bytes: first_bytes,
2643            },
2644            ObjectWrite {
2645                object_id: ObjectId {
2646                    kind: ObjectKind::BlobChunk,
2647                    digest: object_digest(ObjectKind::BlobChunk, &second_bytes),
2648                },
2649                bytes: second_bytes,
2650            },
2651        ];
2652        let receipt = store
2653            .put_many(&writes, WorkBudget::UNBOUNDED, &CancellationToken::new())
2654            .await?;
2655        assert_eq!(receipt.work.backend_write_operations, 1);
2656        assert_eq!(receipt.work.object_bytes_written, 11);
2657        Ok(())
2658    }
2659
2660    #[test]
2661    fn provider_put_batch_interns_identical_object_requests()
2662    -> Result<(), Box<dyn std::error::Error>> {
2663        let bucket = wire::BucketRef {
2664            bucket_id: "bucket".to_owned(),
2665            name: "bucket".to_owned(),
2666        };
2667        let bytes = Bytes::from_static(b"shared");
2668        let object_id = ObjectId {
2669            kind: ObjectKind::BlobChunk,
2670            digest: object_digest(ObjectKind::BlobChunk, &bytes),
2671        };
2672        let writes = [
2673            ObjectWrite {
2674                object_id,
2675                bytes: bytes.clone(),
2676            },
2677            ObjectWrite { object_id, bytes },
2678        ];
2679        let (requests, unique_writes, work) = prepare_provider_put_batch(
2680            &bucket,
2681            &writes,
2682            WorkBudget::UNBOUNDED,
2683            &CancellationToken::new(),
2684        )?;
2685        assert_eq!(requests.len(), 1);
2686        assert_eq!(unique_writes, [0]);
2687        assert_eq!(work.object_bytes_written, 12);
2688        assert_eq!(work.bytes_hashed, 12);
2689        Ok(())
2690    }
2691
2692    #[test]
2693    fn provider_put_batch_rejects_unadmitted_request_allocation() {
2694        let bucket = wire::BucketRef {
2695            bucket_id: "bucket".to_owned(),
2696            name: "bucket".to_owned(),
2697        };
2698        let bytes = Bytes::from_static(b"body");
2699        let writes = [ObjectWrite {
2700            object_id: ObjectId {
2701                kind: ObjectKind::BlobChunk,
2702                digest: object_digest(ObjectKind::BlobChunk, &bytes),
2703            },
2704            bytes,
2705        }];
2706        let mut budget = WorkBudget::UNBOUNDED;
2707        budget.allocation_operations = 0;
2708        let failure =
2709            prepare_provider_put_batch(&bucket, &writes, budget, &CancellationToken::new())
2710                .err()
2711                .unwrap_or_else(|| {
2712                    OperationFailure::before_work(ObjectStoreError::Rejected(
2713                        "unadmitted allocation unexpectedly succeeded".to_owned(),
2714                    ))
2715                });
2716        assert!(matches!(failure.error, ObjectStoreError::Work(_)));
2717        assert_eq!(*failure.work, WorkCounters::default());
2718    }
2719
2720    #[test]
2721    fn generation_records_are_bound_to_their_authority_and_sequence()
2722    -> Result<(), Box<dyn std::error::Error>> {
2723        let volume_id = crate::foundation::VolumeId::from_bytes([0x71; 16]);
2724        let authority_id = volume_authority_id(volume_id);
2725        let root = ObjectId {
2726            kind: ObjectKind::GenerationRoot,
2727            digest: Digest::from_bytes([0x72; 32]),
2728        };
2729        let creation = encode_volume_created(VolumeCreated {
2730            volume_id,
2731            config: VolumeConfig::portable(Lifecycle::Ephemeral),
2732            initial_generation_root: root,
2733        })?;
2734        let publication = encode_publication_payload(volume_id, root);
2735        let generation = GenerationId::new(root.digest);
2736
2737        assert_eq!(
2738            generation_from_payload(authority_id, Sequence::new(1), &creation)?,
2739            Some(generation)
2740        );
2741        assert_eq!(
2742            generation_from_payload(authority_id, Sequence::new(2), &publication)?,
2743            Some(generation)
2744        );
2745        assert!(generation_from_payload(authority_id, Sequence::new(2), &creation).is_err());
2746        assert!(generation_from_payload(authority_id, Sequence::new(1), &publication).is_err());
2747        assert!(
2748            generation_from_payload(
2749                AuthorityId::from_bytes([0x73; 16]),
2750                Sequence::new(1),
2751                &creation,
2752            )
2753            .is_err()
2754        );
2755        assert!(
2756            generation_from_payload(
2757                AuthorityId::from_bytes([0x73; 16]),
2758                Sequence::new(2),
2759                &publication,
2760            )
2761            .is_err()
2762        );
2763        Ok(())
2764    }
2765}