Skip to main content

alkgit_substrate/
compose.rs

1//! The single composition entry (wave 8) — the complete backend
2//! assembly (substrate.md §public API: "construction wired by a single
3//! composition entry that takes the substrate engine handles (the
4//! assembly-level one-statement engine choice)"; substrate.md §assembly
5//! shapes: "Engine choice is one assembly-level statement — three
6//! shapes, zero assembly code").
7//!
8//! [`SubstrateAssembly`] is the wired whole: the five backend-trait
9//! implementations as `Arc<dyn ...>` (the core's seam types —
10//! `GitRegistry`, `GitRegistryStore`, `GitRefs`, `GitPackGen`,
11//! `GitPackIngest`) plus the machinery's handles the substrate doc's
12//! members list names (the object front, the reactor, the maintenance
13//! chain, the applied watermark). One fallible constructor per
14//! assembly, per the [`OpenError`] posture:
15//!
16//! - [`SubstrateAssembly::mem`] — the completeness shape. No engine
17//!   dependency: the registry adapter, the refs table, and every
18//!   wiring piece are the crate's own base types, and the constructor
19//!   *takes the substrate engine handles* — the alkstore engine the
20//!   reactive halves ride and the alkblobs object pool the git-object
21//!   family lives under (`alkstore-mem` + `Store::mem_only()` in the
22//!   tests' column; any `Store` pairing for an embedder — the reactive
23//!   composition is engine-generic, POC-R's posture). Nothing opens,
24//!   so the failure set is only the wiring legs (the liveness
25//!   registration).
26//! - [`SubstrateAssembly::sqlite`] — the default single-host assembly
27//!   per substrate.md §assembly shapes row one: one sqlite file
28//!   co-tenant by the registry/ref tables
29//!   ([`crate::sqlite::SqliteRegistryAssembly`]'s constructor), the
30//!   alkstore-sqlite engine, and the alkblobs dual-tier sqlite object
31//!   store (`dual_tier_sqlite_local` — the large tier the through-store
32//!   serving shape and over-threshold objects need; a sqlite fleet is a
33//!   recorded non-option, alkblobs ADR-010 §3). `:memory:` never
34//!   reaches an engine — the DQ-7 ruling holds by construction: every
35//!   open rides the same file, so the ephemeral shape is the mem
36//!   assembly, never sqlite-in-memory.
37//! - the `postgres` constructor — the fleet/durable-shared
38//!   assembly (the `postgres` feature; substrate.md §assembly shapes
39//!   row two): the DSN every open shares — the co-tenant connection
40//!   (`CoTenantPg`, the registry's own connection,
41//!   the fourth pool of the assembly budget), the alkstore-postgres
42//!   engine, and the alkblobs dual-tier postgres object store
43//!   (`dual_tier_postgres_local` — pg kv + a local large tier; the
44//!   consolidation pg-lo posture is a deployment choice the entry does
45//!   not make for the embedder).
46//!
47//! Wave 6's per-piece constructors are unified behind the entry, not
48//! duplicated: the engine-twin entries call the twin assemblies'
49//! constructors and consume their exposed wiring; the complete-backend
50//! wiring legs (the object front, the machinery custody share, the
51//! pack serving shapes, the liveness registration, the maintenance
52//! chain) run once in `wire` for all three shapes.
53//!
54//! # The entry's construction decisions (the task's spec-level call,
55//! recorded for the OQ-03 inventory)
56//!
57//! - **What the engine handles are** (the opened-assembly-handle vs
58//!   config question): the sqlite/pg entries open with *config* — the
59//!   assembly-level engine statement is the constructor call itself
60//!   (alkstore ADR-016's deployment honesty; co-tenancy on one file /
61//!   one DSN is guaranteed by construction, never an embedder duty) —
62//!   and the mem entry takes the opened handles (no engine dependency,
63//!   nothing to open).
64//! - **What the machinery returns**: handles, not run tasks — the
65//!   reactor/chain runtime legs are *returned for the embedder to
66//!   drive*. [`MaintenanceChain::run`](crate::maintenance::MaintenanceChain::run)
67//!   owns the supervision (the sweep schedule + the scheduler leg, the
68//!   supervised wake runner, the reconcile tick, the worker claim
69//!   loop) under one caller-owned stop token. No task spawns at
70//!   construction; the entry constructs and wires.
71//! - **Both pack serving shapes at the entry** (the wave-7 posture,
72//!   assembly-level): [`AssemblyOptions::pack_serving`] picks
73//!   [`PackServing::Direct`] (the default — no storage round-trip) or
74//!   [`PackServing::ThroughStore`] (put-once + ranged windows with
75//!   mid-flight digest verification; the request-digest cache is
76//!   shared with the maintenance chain's pack-housekeeping release
77//!   path). The large tier the through-store shape requires is part of
78//!   the engine-twin object stores by construction; a mem object pool
79//!   without one rejects over-threshold packs through the store's own
80//!   put-time posture.
81//!
82//! # The receive-pack flow, as wired
83//!
84//! The entry's refs leg is the w8 custody/commit side: the refs table
85//! wrapped in the liveness write counter
86//! (`VersionedRefTable`)) and then in
87//! the composition-internal `CommittedRefs` — stale-prune → CAS →
88//! drain-only-after-commit, then the per-push mutation-marker write +
89//! post-commit announce (the `RegistryMutations` legs surface: the mem
90//! shape's fused legs or the engine-twin assemblies' fused
91//! `record_mutation`). The [`PackIngest`]
92//! shares the pending-pin registry the wrapped refs impl drains —
93//! constructed together, the custody seam's one share (substrate.md
94//! §object storage, "Pin custody across the frozen trait seam"). The
95//! wire layer reaches the backend only through the trait objects, so
96//! this assembly is the one place the receive flow's legs exist. The
97//! ref-table-closure liveness source is constructed over the front +
98//! the wrapped table's `RefRows` face + the same write counter and
99//! registered on the object pool — zero registered sources make every
100//! sweep abort (`GcAbortCause::NoLivenessSources`). The LFS media
101//! source (`lfs`, the wave-9 wiring) registers beside it — the second
102//! leg over the wrapped LFS rows surface and its assert counter, the
103//! fail-safe default (ADR-022 §6: the `lfs` feature gates the
104//! registration itself, so the source rides by construction and a
105//! sweep never eats referenced media on a mixed deployment).
106//! The twins' record-transaction legs (the unborn-HEAD seed at record
107//! creation, the repo's ref rows leaving with its removal — substrate.md
108//! §refs) ride the composition on the mem shape too now that the entry
109//! wires it a refs table (`MemSeededBackend`; the twins carry their
110//! own in-SQL copies).
111//!
112//! # Names pinned here (the OQ-03 substrate-side inventory for this task)
113//!
114//! `SubstrateAssembly` (`mem`/`sqlite`/`postgres` + the accessors),
115//! `AssemblyOptions`, `PackServing` — plus the wave-9 rows task's
116//! accessor: `lfs_rows()` (the `lfs` feature's row-op surface, the
117//! `LfsRows` trait object whatever the shape — the rows surface the
118//! entry wraps in the assert counter) and the wave-9 liveness task's
119//! `lfs_registration()` (the second registration leg's token). The
120//! receive-pack composition pieces themselves are deliberately not in
121//! the inventory (substrate.md §public API: internal to the trait
122//! impls).
123
124#[cfg(feature = "sqlite")]
125use std::path::Path;
126use std::path::PathBuf;
127use std::sync::atomic::{AtomicU64, Ordering};
128use std::sync::Arc;
129
130use alkstore::Store;
131use async_trait::async_trait;
132
133use crate::error::OpenError;
134use crate::ingest::PackIngest;
135#[cfg(feature = "lfs")]
136use crate::lfs::LfsRows as _;
137use crate::liveness::RefClosure;
138use crate::maintenance::{MaintenanceChain, MaintenanceOpts};
139use crate::odb::ObjectFront;
140use crate::packgen::{PackCache, PackGen};
141use crate::pins::{CommittedRefs, MemMutationLegs, PendingPinRegistry, RegistryMutations};
142use crate::reactor::{AppliedWatermark, Reactor};
143use crate::refs::InMemoryRefTable;
144use crate::registry::{
145    CachedRegistryProvider, InMemoryRegistry, RegistryBackend, RegistryWrite, HEAD_SEED_TARGET,
146};
147use alkgit::{RegistryError, RepoRecord};
148
149use alkblobs::Store as BlobStore;
150
151#[cfg(feature = "lfs")]
152use crate::liveness::LfsMedia;
153
154use alkgit::{GitPackGen, GitPackIngest, GitRefs, GitRegistry, GitRegistryStore};
155
156/// The pack generation's serving shape at the entry (substrate.md
157/// §object storage "Packs": "Two serving shapes, both validated").
158#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
159pub enum PackServing {
160    /// The generated pack streams through sideband, no storage
161    /// round-trip. The default.
162    #[default]
163    Direct,
164    /// The pack is put once, then served through ranged windows with
165    /// mid-flight digest verification; the request-digest cache lets
166    /// repeat fetches ride the stored pack. Requires the large tier
167    /// (over-threshold packs stage into it — the engine-twin object
168    /// stores carry it; a mem object pool without one rejects
169    /// over-threshold puts through the store's own posture).
170    ThroughStore,
171}
172
173/// The entry's assembly-level options — the knobs the entry's
174/// constructors carry. Everything else is the shipped default posture
175/// (the object stores' thresholds and window delays are the twin
176/// constructors' own defaults; constructor knobs are additive later —
177/// the two-way-door rule the HEAD seed pinned).
178#[derive(Clone, Debug)]
179pub struct AssemblyOptions {
180    /// The pack generation's serving shape (the default is
181    /// [`PackServing::Direct`]).
182    pub pack_serving: PackServing,
183    /// The receive-pack working area's root — the ingest implement's
184    /// budgeted host scratch ([`PackIngest`]'s constructor knob; a
185    /// fresh per-push directory under it, discarded after unpack).
186    pub work_root: PathBuf,
187    /// The object store's local large tier root — the over-threshold
188    /// staging directory the sqlite/pg entries' object stores construct
189    /// (alkblobs' dual-tier shapes need one). The mem entry's object
190    /// pool rides the handle the embedder supplies instead.
191    pub large_root: PathBuf,
192    /// The maintenance chain's tuning datums (the sweep cadence, the
193    /// reconcile tick, the worker poll, the wake-restart backoff, the
194    /// leader-lock TTL). [`MaintenanceOpts::default()`] is the shipped
195    /// posture.
196    pub maintenance: MaintenanceOpts,
197}
198
199impl AssemblyOptions {
200    /// The options with the given working-area and large-tier roots
201    /// and the shipped default posture everywhere else.
202    pub fn new(work_root: impl Into<PathBuf>, large_root: impl Into<PathBuf>) -> Self {
203        AssemblyOptions {
204            pack_serving: PackServing::default(),
205            work_root: work_root.into(),
206            large_root: large_root.into(),
207            maintenance: MaintenanceOpts::default(),
208        }
209    }
210}
211
212/// The assembly-level pieces the per-assembly constructors wire before
213/// the shared complete-backend wiring runs — the registry machinery
214/// (the wave-6 assembly shapes, unified) plus the refs table and
215/// mutation legs the entry's refs leg wraps.
216struct RegistryLegs {
217    /// The reactive halves' substrate (the maintenance chain rides the
218    /// same handle).
219    store: Arc<dyn Store>,
220    /// The coherently cached hot read.
221    provider: Arc<CachedRegistryProvider>,
222    /// The registry sides, pre-coerced (the record mutations announce
223    /// post-commit and write through; the mem binding carries the
224    /// seeding legs).
225    registry: Arc<dyn GitRegistry>,
226    registry_store: Arc<dyn GitRegistryStore>,
227    /// The announce/wake/reconcile machinery.
228    reactor: Arc<Reactor>,
229    /// The refs table under the write counter — the wrapped refs impl's
230    /// inner and the liveness snapshot's rows.
231    versioned: Arc<dyn GitRefs>,
232    /// The same instance's `RefRows` face (the snapshot source).
233    rows: Arc<dyn crate::liveness::RefRows>,
234    /// The refs writes' counter (the liveness arbitration signal).
235    refs_version: Arc<AtomicU64>,
236    /// The per-push mutation legs (the fused marker + announce).
237    mutations: Arc<dyn RegistryMutations>,
238    /// The LFS pointer-lifecycle row store (`lfs`) under the assert
239    /// counter — the wrapped surface (the batch adapter's assertion
240    /// surface and the wave-9 liveness source's row read; the one
241    /// handle both consume, the counter bump riding every asserted
242    /// row).
243    #[cfg(feature = "lfs")]
244    lfs: Arc<dyn crate::lfs::LfsRows>,
245    /// The LFS rows' counter (the LFS liveness source's arbitration
246    /// signal — the wrapped surface bumps it on every `assert`; the
247    /// source reads it in `contains`).
248    #[cfg(feature = "lfs")]
249    lfs_version: Arc<AtomicU64>,
250}
251
252/// The mem binding's record-transaction legs — the twins' seeding and
253/// removal legs realized at the composition (substrate.md §refs: the
254/// substrate's `put_repo` seeds the unborn HEAD in the record
255/// transaction; repo deletion takes the repo's ref rows with it — and
256/// the repo's LFS liveness rows, the wave-9 rows task's mem-shape
257/// leg). The wave-6 degenerate case had no refs table, so the twins
258/// carried the legs; the entry wires the mem shape a refs table, and
259/// the legs ride here. Not in the OQ-03 inventory (composition-internal).
260struct MemSeededBackend {
261    inner: Arc<RegistryBackend>,
262    rows: Arc<InMemoryRefTable>,
263    writes: Arc<AtomicU64>,
264    #[cfg(feature = "lfs")]
265    lfs: Arc<crate::lfs::VersionedLfsRows<crate::lfs::InMemoryLfsRows>>,
266}
267
268impl RegistryLegs {
269    /// The mem shape's legs: the in-memory registry + table + the
270    /// fused marker legs + the record-transaction seeding legs (the
271    /// base types; the provider write-through).
272    fn mem(store: Arc<dyn Store>) -> Self {
273        let durable = Arc::new(InMemoryRegistry::new());
274        let provider = Arc::new(CachedRegistryProvider::new());
275        let reactor = Arc::new(Reactor::new(
276            Arc::clone(&store),
277            durable.clone(),
278            provider.clone(),
279        ));
280        let announced: Arc<dyn RegistryWrite> = Arc::new(crate::reactor::AnnouncingRegistry::new(
281            durable.clone(),
282            reactor.clone(),
283        ));
284        let refs_version = Arc::new(AtomicU64::new(0));
285        let table = Arc::new(InMemoryRefTable::new());
286        #[cfg(feature = "lfs")]
287        let lfs_version = Arc::new(AtomicU64::new(0));
288        #[cfg(feature = "lfs")]
289        let lfs = Arc::new(crate::lfs::VersionedLfsRows::new(
290            Arc::new(crate::lfs::InMemoryLfsRows::new()),
291            Arc::clone(&lfs_version),
292        ));
293        let inner = Arc::new(RegistryBackend::with_cached_provider(
294            announced,
295            provider.clone(),
296        ));
297        let seeded = Arc::new(MemSeededBackend {
298            inner,
299            rows: Arc::clone(&table),
300            writes: Arc::clone(&refs_version),
301            #[cfg(feature = "lfs")]
302            lfs: Arc::clone(&lfs),
303        });
304        let versioned = Arc::new(crate::refs::VersionedRefTable::new(
305            Arc::clone(&table),
306            Arc::clone(&refs_version),
307        ));
308        let mutations = Arc::new(MemMutationLegs::new(
309            Arc::clone(&durable),
310            Arc::clone(&reactor),
311        ));
312        RegistryLegs {
313            store,
314            provider,
315            registry: Arc::clone(&seeded) as Arc<dyn GitRegistry>,
316            registry_store: seeded as Arc<dyn GitRegistryStore>,
317            reactor,
318            versioned: Arc::clone(&versioned) as Arc<dyn GitRefs>,
319            rows: versioned,
320            refs_version,
321            mutations,
322            #[cfg(feature = "lfs")]
323            lfs: lfs as Arc<dyn crate::lfs::LfsRows>,
324            #[cfg(feature = "lfs")]
325            lfs_version,
326        }
327    }
328}
329
330#[async_trait]
331impl GitRegistryStore for MemSeededBackend {
332    async fn put_repo(&self, record: &RepoRecord) -> Result<(), RegistryError> {
333        self.inner.put_repo(record).await?;
334        self.rows.seed_head(&record.repo_id, HEAD_SEED_TARGET).await;
335        self.writes.fetch_add(1, Ordering::Relaxed);
336        Ok(())
337    }
338
339    async fn update_repo(&self, record: &RepoRecord) -> Result<(), RegistryError> {
340        self.inner.update_repo(record).await
341    }
342
343    async fn remove_repo(&self, repo_id: &str) -> Result<(), RegistryError> {
344        self.inner.remove_repo(repo_id).await?;
345        self.rows.drop_repo_rows(repo_id).await;
346        #[cfg(feature = "lfs")]
347        self.lfs
348            .remove_repo(repo_id)
349            .await
350            .map_err(|e| RegistryError::Io(e.to_string()))?;
351        self.writes.fetch_add(1, Ordering::Relaxed);
352        Ok(())
353    }
354}
355
356#[async_trait]
357impl GitRegistry for MemSeededBackend {
358    async fn resolve(&self, repo_id: &str) -> Result<RepoRecord, RegistryError> {
359        self.inner.resolve(repo_id).await
360    }
361}
362
363/// The complete-backend wiring — the legs every shape's constructor
364/// runs once (the wave-6 per-piece wiring, unified here). Fallible on
365/// the liveness registration alone (the construction's one loud leg).
366async fn wire(
367    legs: RegistryLegs,
368    blobs: Arc<BlobStore>,
369    options: AssemblyOptions,
370) -> Result<SubstrateAssembly, OpenError> {
371    let front = ObjectFront::new(Arc::clone(&blobs));
372    let pins = Arc::new(PendingPinRegistry::new());
373
374    let (packgen, packs) = match options.pack_serving {
375        PackServing::Direct => (PackGen::direct(front.clone()), None),
376        PackServing::ThroughStore => {
377            let cache = Arc::new(PackCache::new(crate::packgen::DEFAULT_CACHE_ENTRIES));
378            (
379                PackGen::through_store_with(front.clone(), Arc::clone(&cache)),
380                Some(cache),
381            )
382        }
383    };
384
385    let committed = CommittedRefs::new(
386        Arc::clone(&legs.versioned),
387        Arc::clone(&pins),
388        Arc::clone(&legs.mutations),
389    );
390    let refs: Arc<dyn GitRefs> = Arc::new(committed);
391
392    let ingest: Arc<dyn GitPackIngest> = Arc::new(PackIngest::new(
393        front.clone(),
394        Arc::clone(&pins),
395        options.work_root.clone(),
396    ));
397
398    let closure = RefClosure::new(front.clone(), legs.rows.clone(), legs.refs_version.clone());
399    let liveness_registration = closure
400        .register(&blobs)
401        .map_err(|e| OpenError::Engine(format!("the liveness source registration: {e}")))?;
402
403    #[cfg(feature = "lfs")]
404    let lfs_registration = {
405        let media = LfsMedia::new(legs.lfs.clone(), legs.lfs_version.clone());
406        media
407            .register(&blobs)
408            .map_err(|e| OpenError::Engine(format!("the LFS liveness source registration: {e}")))?
409    };
410
411    let applied = AppliedWatermark::new();
412    let chain = MaintenanceChain::new(
413        Arc::clone(&legs.store),
414        Arc::clone(&blobs),
415        legs.reactor.clone(),
416        applied.clone(),
417        packs,
418        options.maintenance.clone(),
419    );
420
421    Ok(SubstrateAssembly {
422        registry: legs.registry,
423        registry_store: legs.registry_store,
424        refs,
425        packgen: Arc::new(packgen),
426        ingest,
427        front,
428        reactor: legs.reactor,
429        chain,
430        applied,
431        liveness_registration,
432        #[cfg(feature = "lfs")]
433        lfs_registration,
434        blobs,
435        provider: legs.provider,
436        refs_version: legs.refs_version,
437        #[cfg(feature = "lfs")]
438        lfs_rows: legs.lfs,
439    })
440}
441
442/// The complete wired backend — the single composition entry's product
443/// (substrate.md §public API's members): the five backend-trait
444/// implementations as `Arc<dyn ...>` plus the machinery's handles.
445///
446/// The runtime legs are the embedder's to drive:
447/// [`self.chain().run(worker_id, stop, on_event)`](crate::maintenance::MaintenanceChain::run)
448/// runs the supervision (the sweep schedule, the scheduler leg, the
449/// supervised wake runner — the provider invalidation's delivery, the
450/// reconcile tick, the worker claim loop) under one caller-owned stop
451/// token. Nothing spawns at construction.
452///
453/// The public API = authorized-surface rule: the op and session layers
454/// reach the backend only through the core's traits (the returned
455/// trait objects); the receive-pack composition — the pending-pin
456/// registry and the wrapped refs impl — is internal to the trait
457/// impls (substrate.md §public API's internal-members rule).
458pub struct SubstrateAssembly {
459    registry: Arc<dyn GitRegistry>,
460    registry_store: Arc<dyn GitRegistryStore>,
461    refs: Arc<dyn GitRefs>,
462    packgen: Arc<dyn GitPackGen>,
463    ingest: Arc<dyn GitPackIngest>,
464    front: ObjectFront,
465    reactor: Arc<Reactor>,
466    chain: MaintenanceChain,
467    applied: Arc<AppliedWatermark>,
468    liveness_registration: alkblobs::Registration,
469    #[cfg(feature = "lfs")]
470    lfs_registration: alkblobs::Registration,
471    blobs: Arc<BlobStore>,
472    provider: Arc<CachedRegistryProvider>,
473    refs_version: Arc<AtomicU64>,
474    #[cfg(feature = "lfs")]
475    lfs_rows: Arc<dyn crate::lfs::LfsRows>,
476}
477
478impl SubstrateAssembly {
479    /// The mem assembly — the completeness shape (the batteries-
480    /// included degenerate case: single-process, the in-memory
481    /// registry adapter and refs table, no feature gates, ADR-024 §6).
482    ///
483    /// `store` is the alkstore engine the reactive halves and the
484    /// maintenance chain ride; `blobs` is the object pool the
485    /// git-object family lives under — the substrate engine handles,
486    /// supplied (the mem entry opens nothing; "no engine dependency"
487    /// realized honestly). See the module doc for the failure set.
488    pub async fn mem(
489        store: Arc<dyn Store>,
490        blobs: Arc<BlobStore>,
491        options: AssemblyOptions,
492    ) -> Result<Self, OpenError> {
493        wire(RegistryLegs::mem(store), blobs, options).await
494    }
495
496    /// The sqlite assembly — the default single-host shape
497    /// (substrate.md §assembly shapes row one): one co-tenant file.
498    ///
499    /// `path` is the sqlite file the registry/ref tables, the
500    /// alkstore-sqlite engine, and the alkblobs kv engine share (the
501    /// co-tenancy composition posture; the migrations run at the twin
502    /// assembly's construction, loud — a half-migrated store refuses
503    /// service).
504    #[cfg(feature = "sqlite")]
505    pub async fn sqlite(
506        path: impl AsRef<Path>,
507        options: AssemblyOptions,
508    ) -> Result<Self, OpenError> {
509        let path = path.as_ref();
510        let assembly = crate::sqlite::SqliteRegistryAssembly::open(path).await?;
511        let blobs = Arc::new(
512            BlobStore::dual_tier_sqlite_local(
513                path,
514                options.large_root.clone(),
515                alkblobs::DEFAULT_DISPATCH_THRESHOLD,
516                alkblobs::DEFAULT_DELETE_WINDOW_DELAY,
517            )
518            .await
519            .map_err(|e| OpenError::Engine(format!("alkblobs sqlite object store: {e}")))?,
520        );
521
522        let versioned: Arc<dyn GitRefs> = assembly.refs().clone();
523        let rows: Arc<dyn crate::liveness::RefRows> = assembly.refs().clone();
524        #[cfg(feature = "lfs")]
525        let lfs_version = assembly.lfs_version().clone();
526        #[cfg(feature = "lfs")]
527        let lfs_rows: Arc<dyn crate::lfs::LfsRows> = assembly.lfs_rows().clone();
528        let legs = RegistryLegs {
529            store: assembly.store().clone(),
530            provider: assembly.provider().clone(),
531            registry: assembly.backend().clone() as Arc<dyn GitRegistry>,
532            registry_store: assembly.backend().clone() as Arc<dyn GitRegistryStore>,
533            reactor: assembly.reactor().clone(),
534            versioned,
535            rows,
536            refs_version: assembly.refs_version().clone(),
537            mutations: Arc::new(assembly),
538            #[cfg(feature = "lfs")]
539            lfs: lfs_rows.clone(),
540            #[cfg(feature = "lfs")]
541            lfs_version,
542        };
543        wire(legs, blobs, options).await
544    }
545
546    /// The postgres assembly — the fleet/durable-shared shape (the
547    /// `postgres` feature; substrate.md §assembly shapes row two).
548    ///
549    /// `config` is the DSN every open shares. `registry` carries the
550    /// co-tenant schema + the alkstore-postgres engine's options
551    /// ([`crate::postgres::PgRegistryOptions`]); the alkblobs kv
552    /// engine rides its own shipped-default `PgOpts` (knobs additive
553    /// later, the pinned posture).
554    #[cfg(feature = "postgres")]
555    pub async fn postgres(
556        config: &str,
557        registry: crate::postgres::PgRegistryOptions,
558        options: AssemblyOptions,
559    ) -> Result<Self, OpenError> {
560        let assembly = crate::postgres::PgRegistryAssembly::open(config, registry.clone()).await?;
561        let blobs = Arc::new(
562            BlobStore::dual_tier_postgres_local(
563                config,
564                registry.blobs,
565                options.large_root.clone(),
566                alkblobs::DEFAULT_DISPATCH_THRESHOLD,
567                alkblobs::DEFAULT_DELETE_WINDOW_DELAY,
568            )
569            .await
570            .map_err(|e| OpenError::Engine(format!("alkblobs postgres object store: {e}")))?,
571        );
572
573        let versioned: Arc<dyn GitRefs> = assembly.refs().clone();
574        let rows: Arc<dyn crate::liveness::RefRows> = assembly.refs().clone();
575        #[cfg(feature = "lfs")]
576        let lfs_version = assembly.lfs_version().clone();
577        #[cfg(feature = "lfs")]
578        let lfs_rows: Arc<dyn crate::lfs::LfsRows> = assembly.lfs_rows().clone();
579        let legs = RegistryLegs {
580            store: assembly.store().clone(),
581            provider: assembly.provider().clone(),
582            registry: assembly.backend().clone() as Arc<dyn GitRegistry>,
583            registry_store: assembly.backend().clone() as Arc<dyn GitRegistryStore>,
584            reactor: assembly.reactor().clone(),
585            versioned,
586            rows,
587            refs_version: assembly.refs_version().clone(),
588            mutations: Arc::new(assembly),
589            #[cfg(feature = "lfs")]
590            lfs: lfs_rows.clone(),
591            #[cfg(feature = "lfs")]
592            lfs_version,
593        };
594        wire(legs, blobs, options).await
595    }
596}
597
598// The assembly's accessors (the OQ-03 inventory's member functions).
599
600impl SubstrateAssembly {
601    /// The registry read side ([`GitRegistry`] — the resolve hot
602    /// path, served through the cached provider).
603    pub fn registry(&self) -> &Arc<dyn GitRegistry> {
604        &self.registry
605    }
606
607    /// The registry store side ([`GitRegistryStore`] — the record
608    /// mutations, announcing post-commit and writing through).
609    pub fn registry_store(&self) -> &Arc<dyn GitRegistryStore> {
610        &self.registry_store
611    }
612
613    /// The refs side ([`GitRefs`]) — the receive flow's committed-ref
614    /// side: stale-prune → CAS → drain-only-after-commit, then the
615    /// per-push marker + announce legs.
616    pub fn refs(&self) -> &Arc<dyn GitRefs> {
617        &self.refs
618    }
619
620    /// The pack generation side ([`GitPackGen`], assembled in the
621    /// configured serving shape).
622    pub fn packgen(&self) -> &Arc<dyn GitPackGen> {
623        &self.packgen
624    }
625
626    /// The pack ingest side ([`GitPackIngest`] — the receive path's
627    /// prepare leg, custody via the shared pending-pin registry).
628    pub fn ingest(&self) -> &Arc<dyn GitPackIngest> {
629        &self.ingest
630    }
631
632    /// The object front (constructed once; per-session
633    /// [`SessionFind`](crate::odb::ObjectFront::session) views).
634    pub fn front(&self) -> &ObjectFront {
635        &self.front
636    }
637
638    /// The reactor (the announce machinery; the stream consumer and
639    /// the reconcile sweep's home for replication-style embedders).
640    pub fn reactor(&self) -> &Arc<Reactor> {
641        &self.reactor
642    }
643
644    /// The maintenance chain (the embedder drives
645    /// [`MaintenanceChain::run`]).
646    pub fn chain(&self) -> &MaintenanceChain {
647        &self.chain
648    }
649
650    /// The composition's applied watermark — the same handle the
651    /// reconcile tick advances; hand it to
652    /// [`Reactor::run_stream_consumer`] alongside.
653    pub fn applied_watermark(&self) -> &Arc<AppliedWatermark> {
654        &self.applied
655    }
656
657    /// The cached read provider (the hot read's home — replication
658    /// and observation consumers).
659    pub fn provider(&self) -> &Arc<CachedRegistryProvider> {
660        &self.provider
661    }
662
663    /// The object pool (the git-object family's home; the LFS
664    /// surface's base).
665    pub fn blobs(&self) -> &Arc<BlobStore> {
666        &self.blobs
667    }
668
669    /// The liveness source's registration token (the composition holds
670    /// it for the store's lifetime; deregister/re-register is the
671    /// embedder's ADR-012 §6.4 correction flow).
672    pub fn liveness_registration(&self) -> &alkblobs::Registration {
673        &self.liveness_registration
674    }
675
676    /// The LFS media source's registration token (`lfs` — the second
677    /// registration leg, registered beside the ref-table closure's at
678    /// the composition entry; the fail-safe default ADR-022 §6). The
679    /// same lifetime posture: the composition holds it, the
680    /// deregister/re-register correction flow is the embedder's.
681    #[cfg(feature = "lfs")]
682    pub fn lfs_registration(&self) -> &alkblobs::Registration {
683        &self.lfs_registration
684    }
685
686    /// The LFS pointer-lifecycle row store (the `lfs` feature's
687    /// row-op surface): the batch adapter's assertion surface (rows
688    /// asserted at check/upload time — lfs.md §3) and the LFS
689    /// liveness source's read (all rows / per-repo rows — the
690    /// re-protection contract's refcount data; the wave-9 liveness
691    /// task consumes this accessor). The surface is the wrapped shape
692    /// on every source ([`crate::lfs::VersionedLfsRows`] — every
693    /// `assert` bumps [`Self::lfs_registration`]'s counter): on the
694    /// engine assemblies the wrap is the twins' own (they carry it at
695    /// construction; the entry composes it unchanged, no double wrap).
696    #[cfg(feature = "lfs")]
697    pub fn lfs_rows(&self) -> &Arc<dyn crate::lfs::LfsRows> {
698        &self.lfs_rows
699    }
700}
701
702/// The refs writes' shared counter — bumped by every CAS call through
703/// the refs side; the liveness source's arbitration epoch signal
704/// (observation data).
705impl SubstrateAssembly {
706    /// The counter's current value.
707    pub fn refs_version(&self) -> u64 {
708        self.refs_version.load(Ordering::Relaxed)
709    }
710}
711#[cfg(test)]
712mod tests {
713    use std::collections::BTreeMap;
714    use std::sync::atomic::AtomicU64;
715    use std::sync::Arc;
716    use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
717
718    use alkcall::core::Identity;
719    use alkstore::Store as AlkStore;
720    use gix_hash::{hasher, Kind as HashKind, ObjectId};
721    use gix_object::Kind;
722    use gix_pack::data::entry::Header as PackHeader;
723    use gix_pack::data::input::Entry as PackEntry;
724    use tokio::io::{duplex, AsyncReadExt, AsyncWriteExt};
725    use tokio::sync::Semaphore;
726
727    use alkgit::{
728        serve_duplex_resolved, GitPackGen, GitPackIngest, GitRefs, GitRegistry, Limits,
729        RegistryError, Service, SubstrateError, Visibility,
730    };
731    use serde_json::Value;
732
733    use alkgit::RepoRecord;
734
735    use super::*;
736    use crate::loose_form;
737    use crate::registry::RegistryRead as _;
738
739    fn unique_root(tag: &str) -> std::path::PathBuf {
740        static COUNTER: AtomicU64 = AtomicU64::new(0);
741        let n = COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
742        let nanos = SystemTime::now()
743            .duration_since(UNIX_EPOCH)
744            .map(|d| d.as_nanos())
745            .unwrap_or_default();
746        std::env::temp_dir().join(format!(
747            "alkgit-substrate-compose-{tag}-{}-{n}-{nanos}",
748            std::process::id()
749        ))
750    }
751
752    fn identity() -> Identity {
753        Identity {
754            id: "u-writer".to_owned(),
755            scopes: Vec::new(),
756            resources: Default::default(),
757        }
758    }
759
760    fn granted_record(id: &str, identity: &Identity) -> RepoRecord {
761        RepoRecord {
762            repo_id: id.to_owned(),
763            visibility: Visibility::Private,
764            grants: BTreeMap::from([(
765                identity.id.clone(),
766                vec![alkgit::AccessAction::Read, alkgit::AccessAction::Write],
767            )]),
768        }
769    }
770
771    fn ungranted_record(id: &str) -> RepoRecord {
772        RepoRecord {
773            repo_id: id.to_owned(),
774            visibility: Visibility::Private,
775            grants: BTreeMap::new(),
776        }
777    }
778
779    fn mem_object_pool() -> Arc<alkblobs::Store> {
780        Arc::new(alkblobs::Store::mem_only())
781    }
782
783    // ——— the client-side session helpers (the core tests' shapes) ———
784
785    fn pkt(payload: &[u8]) -> Vec<u8> {
786        let total = payload.len() + 4;
787        let mut out = Vec::with_capacity(total);
788        out.extend_from_slice(format!("{total:04x}").as_bytes());
789        out.extend_from_slice(payload);
790        out
791    }
792
793    async fn read_pkts<R: tokio::io::AsyncRead + Unpin>(reader: &mut R) -> Vec<Vec<u8>> {
794        let mut packets = Vec::new();
795        loop {
796            let mut header = [0u8; 4];
797            reader.read_exact(&mut header).await.expect("pkt header");
798            let len = usize::from_str_radix(std::str::from_utf8(&header).expect("hex header"), 16)
799                .expect("hex length");
800            if len < 4 {
801                packets.push(Vec::new());
802                break;
803            }
804            let mut payload = vec![0u8; len - 4];
805            reader.read_exact(&mut payload).await.expect("pkt payload");
806            packets.push(payload);
807        }
808        packets
809    }
810
811    fn text(payload: &[u8]) -> String {
812        String::from_utf8_lossy(payload)
813            .trim_end_matches('\n')
814            .to_owned()
815    }
816
817    fn create_command(name: &str, new: ObjectId) -> String {
818        format!("{} {new} {name}", "0".repeat(40))
819    }
820
821    /// Reads the response body until the outer flush, collecting the
822    /// band-1 payloads (sideband framing).
823    async fn read_band1_body<R: tokio::io::AsyncRead + Unpin>(reader: &mut R) -> Vec<u8> {
824        let mut payload = Vec::new();
825        loop {
826            let mut header = [0u8; 4];
827            reader.read_exact(&mut header).await.expect("pkt header");
828            let len = usize::from_str_radix(std::str::from_utf8(&header).expect("hex header"), 16)
829                .expect("hex length");
830            if len < 4 {
831                break;
832            }
833            let mut packet = vec![0u8; len - 4];
834            reader.read_exact(&mut packet).await.expect("pkt payload");
835            assert_eq!(packet[0], 1, "band-1 framing");
836            payload.extend_from_slice(&packet[1..]);
837        }
838        payload
839    }
840
841    fn report_lines(body: &[u8]) -> Vec<String> {
842        let inner = {
843            let mut lines = Vec::new();
844            let mut cursor = body;
845            while !cursor.is_empty() {
846                let len = usize::from_str_radix(
847                    std::str::from_utf8(&cursor[..4]).expect("hex header"),
848                    16,
849                )
850                .expect("hex length");
851                cursor = &cursor[4..];
852                if len < 4 {
853                    lines.push(Vec::new());
854                    continue;
855                }
856                lines.push(cursor[..len - 4].to_vec());
857                cursor = &cursor[len - 4..];
858            }
859            let last = lines.pop().expect("inner framing non-empty");
860            assert!(last.is_empty(), "the inner flush ends the report");
861            lines
862        };
863        inner.iter().map(|p| text(p)).collect()
864    }
865
866    // ——— the fixture chain + its pack (the ingest tests' shapes) ———
867
868    fn tree_bytes(entries: &[(&str, &ObjectId)]) -> Vec<u8> {
869        let mut tree = Vec::new();
870        for (name, oid) in entries {
871            tree.extend_from_slice(format!("100644 {name}\0").as_bytes());
872            tree.extend_from_slice(oid.as_bytes());
873        }
874        tree
875    }
876
877    fn commit_bytes(tree: &ObjectId, parents: &[&ObjectId]) -> Vec<u8> {
878        let mut body = format!("tree {tree}\n");
879        for parent in parents {
880            body.push_str(&format!("parent {parent}\n"));
881        }
882        body.push_str(
883            "author A <a@example.invalid> 1750000000 +0000\n\
884             committer A <a@example.invalid> 1750000000 +0000\n\nthe message\n",
885        );
886        body.into_bytes()
887    }
888
889    fn oid_of(kind: Kind, content: &[u8]) -> ObjectId {
890        let form = loose_form(kind, content);
891        let mut h = hasher(HashKind::Sha1);
892        h.update(&form);
893        h.try_finalize().expect("fixture oid hashes")
894    }
895
896    fn deflate(bytes: &[u8]) -> Vec<u8> {
897        let mut deflate =
898            gix_zlib::stream::deflate::Write::new(Vec::new(), gix_zlib::Compression::DEFAULT);
899        std::io::Write::write_all(&mut deflate, bytes).expect("fixture bytes deflate");
900        std::io::Write::flush(&mut deflate).expect("the fixture deflate stream finishes");
901        deflate.into_inner()
902    }
903
904    fn pack_entry(kind: Kind, body: &[u8], pack_offset: u64) -> PackEntry {
905        let header = match kind {
906            Kind::Blob => PackHeader::Blob,
907            Kind::Tree => PackHeader::Tree,
908            Kind::Commit => PackHeader::Commit,
909            Kind::Tag => PackHeader::Tag,
910        };
911        let compressed = deflate(body);
912        PackEntry {
913            header,
914            header_size: header.size(body.len() as u64) as u16,
915            pack_offset,
916            compressed: Some(compressed.clone()),
917            compressed_size: compressed.len() as u64,
918            crc32: None,
919            decompressed_size: body.len() as u64,
920            trailer: None,
921        }
922    }
923
924    fn pack_bytes(entries: &[PackEntry]) -> Vec<u8> {
925        let mut out = Vec::new();
926        out.extend_from_slice(&gix_pack::data::header::encode(
927            gix_pack::data::Version::V2,
928            entries.len() as u32,
929        ));
930        for entry in entries {
931            entry
932                .header
933                .write_to(entry.decompressed_size, &mut out)
934                .expect("fixture entry headers write");
935            out.extend_from_slice(
936                entry
937                    .compressed
938                    .as_ref()
939                    .expect("fixture entries keep bytes"),
940            );
941        }
942        let mut h = hasher(HashKind::Sha1);
943        h.update(&out);
944        out.extend_from_slice(
945            h.try_finalize()
946                .expect("fixture pack trailer hashes")
947                .as_slice(),
948        );
949        out
950    }
951
952    /// A two-commit chain fixture: (pack, tip, base, tree_tip, blob).
953    fn chain_pack() -> (Vec<u8>, ObjectId, ObjectId, ObjectId, ObjectId) {
954        let blob_body = b"the composed chain's blob\n".to_vec();
955        let blob = oid_of(Kind::Blob, &blob_body);
956        let tree_base_body = tree_bytes(&[("a.txt", &blob)]);
957        let tree_base = oid_of(Kind::Tree, &tree_base_body);
958        let base_body = commit_bytes(&tree_base, &[]);
959        let base = oid_of(Kind::Commit, &base_body);
960        let tree_tip_body = tree_bytes(&[("a.txt", &blob), ("b.txt", &blob)]);
961        let tree_tip = oid_of(Kind::Tree, &tree_tip_body);
962        let tip_body = commit_bytes(&tree_tip, &[&base]);
963        let tip = oid_of(Kind::Commit, &tip_body);
964
965        let mut offset = 0u64;
966        let mut entries = Vec::new();
967        for (kind, body) in [
968            (Kind::Blob, blob_body.as_slice()),
969            (Kind::Tree, tree_base_body.as_slice()),
970            (Kind::Commit, base_body.as_slice()),
971            (Kind::Tree, tree_tip_body.as_slice()),
972            (Kind::Commit, tip_body.as_slice()),
973        ] {
974            let entry = pack_entry(kind, body, offset);
975            offset += entry.header_size as u64 + entry.compressed_size;
976            entries.push(entry);
977        }
978        (pack_bytes(&entries), tip, base, tree_tip, blob)
979    }
980
981    // ——— the in-process serving entry ———
982
983    #[allow(clippy::too_many_arguments)]
984    async fn run_push(
985        repo_id: &str,
986        identity: &Identity,
987        registry: Arc<dyn GitRegistry>,
988        refs: Arc<dyn GitRefs>,
989        packgen: Arc<dyn GitPackGen>,
990        ingest: Arc<dyn GitPackIngest>,
991        commands: &[String],
992        pack: &[u8],
993    ) -> Vec<String> {
994        let limits = Limits::default();
995        let (mut peer, stream) = duplex(1 << 16);
996        let joined = tokio::spawn({
997            let registry = registry.clone();
998            let refs = refs.clone();
999            let packgen = packgen.clone();
1000            let ingest = ingest.clone();
1001            let identity = identity.clone();
1002            let repo_id = repo_id.to_owned();
1003            let pipeline = Arc::new(Semaphore::new(limits.max_concurrent_blocking_tasks));
1004            async move {
1005                serve_duplex_resolved(
1006                    &repo_id,
1007                    Some(&identity),
1008                    Service::ReceivePack,
1009                    stream,
1010                    limits,
1011                    registry,
1012                    refs,
1013                    packgen,
1014                    ingest,
1015                    false,
1016                    pipeline,
1017                )
1018                .await
1019            }
1020        });
1021
1022        read_pkts(&mut peer).await;
1023        let mut chunks = Vec::new();
1024        for (i, command) in commands.iter().enumerate() {
1025            if i == 0 {
1026                chunks.push(pkt(
1027                    format!("{command}\0report-status side-band-64k").as_bytes()
1028                ));
1029            } else {
1030                chunks.push(pkt(command.as_bytes()));
1031            }
1032        }
1033        chunks.push(b"0000".to_vec());
1034        chunks.push(pack.to_vec());
1035        peer.write_all(&chunks.concat()).await.unwrap();
1036
1037        let body = read_band1_body(&mut peer).await;
1038        drop(peer);
1039        joined
1040            .await
1041            .expect("the session task joins")
1042            .expect("the push session serves cleanly");
1043        report_lines(&body)
1044    }
1045
1046    async fn feed_events(
1047        assembly: &SubstrateAssembly,
1048        since: i64,
1049    ) -> Vec<crate::reactor::RegistryEvent> {
1050        let stream = assembly
1051            .reactor()
1052            .store()
1053            .stream(crate::reactor::EVENTS_STREAM)
1054            .await
1055            .expect("the feed opens");
1056        stream
1057            .read_since(since, 100)
1058            .await
1059            .expect("the feed reads")
1060            .iter()
1061            .filter_map(crate::reactor::RegistryEvent::decode_event)
1062            .collect()
1063    }
1064
1065    // ——— the mem shape ———
1066
1067    async fn mem_assembly(tag: &str) -> SubstrateAssembly {
1068        let root = unique_root(tag);
1069        let options = AssemblyOptions::new(root.join("work"), root.join("large"));
1070        SubstrateAssembly::mem(
1071            Arc::new(alkstore_mem::MemStore::new()),
1072            mem_object_pool(),
1073            options,
1074        )
1075        .await
1076        .expect("the mem assembly composes")
1077    }
1078
1079    /// The composition's completeness shape: the five trait impls and
1080    /// the machinery handles are wired; the registry pair resolves
1081    /// through the cached provider (write-through); the unborn HEAD
1082    /// line lists through the refs side.
1083    #[tokio::test(flavor = "multi_thread")]
1084    async fn the_mem_assembly_wires_the_complete_backend() {
1085        let assembly = mem_assembly("wired").await;
1086        let identity = identity();
1087        let record = granted_record("owner/composed", &identity);
1088
1089        assembly.registry_store().put_repo(&record).await.unwrap();
1090        assert_eq!(
1091            assembly.registry().resolve("owner/composed").await.unwrap(),
1092            record,
1093            "the provider was written through by the store"
1094        );
1095        assert!(assembly.provider().resolve("owner/composed").is_some());
1096        assert_eq!(
1097            assembly.refs_version(),
1098            1,
1099            "the record-create seeding leg bumped the counter"
1100        );
1101
1102        let lines = assembly
1103            .refs()
1104            .list_refs(&record)
1105            .await
1106            .expect("the refs side lists");
1107        assert_eq!(
1108            lines,
1109            vec![alkgit::RefLine {
1110                name: "HEAD".to_owned(),
1111                oid: None,
1112                peeled: None,
1113                symref_target: Some(crate::registry::HEAD_SEED_TARGET.to_owned()),
1114            }],
1115            "the unborn HEAD shape rides the composed refs — the record's seed row"
1116        );
1117
1118        let unknown = assembly.registry().resolve("owner/ghost").await;
1119        assert_eq!(unknown.unwrap_err(), RegistryError::NotFound);
1120    }
1121
1122    /// The reactive halves re-landed through the entry: the announced
1123    /// record write fires the post-commit legs — the durable feed
1124    /// event, the wake, and the maintenance-queue job.
1125    #[tokio::test(flavor = "multi_thread")]
1126    async fn the_mem_assemblys_reactive_legs_fire_post_commit() {
1127        let assembly = mem_assembly("legs").await;
1128        let identity = identity();
1129        let mut listener = assembly
1130            .reactor()
1131            .store()
1132            .listen(crate::reactor::WAKE_CHANNEL)
1133            .await
1134            .expect("the wake source opens");
1135        let record = granted_record("owner/announced", &identity);
1136
1137        assembly.registry_store().put_repo(&record).await.unwrap();
1138
1139        let events = feed_events(&assembly, 0).await;
1140        assert_eq!(
1141            events,
1142            vec![crate::reactor::RegistryEvent::Put {
1143                seq: 1,
1144                record: record.clone()
1145            }],
1146            "the announced put published post-commit"
1147        );
1148        let wake = listener
1149            .recv_timeout(Duration::from_secs(2))
1150            .await
1151            .expect("the wake delivers")
1152            .expect("wake source open");
1153        assert_eq!(wake.channel, crate::reactor::WAKE_CHANNEL);
1154        drop(listener);
1155
1156        let queue = assembly
1157            .reactor()
1158            .store()
1159            .queue(
1160                crate::reactor::MAINTENANCE_QUEUE,
1161                alkstore::QueueOpts::default(),
1162            )
1163            .await
1164            .expect("the queue opens");
1165        let claimed = queue
1166            .claim_one("probe")
1167            .await
1168            .expect("claim checks")
1169            .expect("the enqueue leg landed the housekeeping job");
1170        assert_eq!(
1171            claimed
1172                .job()
1173                .payload_as::<crate::reactor::MaintenanceJob>()
1174                .unwrap(),
1175            crate::reactor::MaintenanceJob::PackHousekeeping {
1176                repo_id: "owner/announced".to_owned(),
1177            }
1178        );
1179    }
1180
1181    /// The receive path composes through the entry end-to-end
1182    /// in-process: resolve → authorize (the core's types, the granted
1183    /// identity rides) → the serving machinery → the refs commit side
1184    /// (custody + marker + announce) → ingest. The custody observable:
1185    /// the pushed chain is liveness-covered post-commit (a sweep
1186    /// deletes nothing); the legs observable: the `Mutation` marker
1187    /// event anchors the watermark.
1188    #[tokio::test(flavor = "multi_thread")]
1189    async fn the_receive_path_composes_through_the_entry_end_to_end() {
1190        let assembly = mem_assembly("receipt").await;
1191        let identity = identity();
1192        let record = granted_record("owner/pushed", &identity);
1193        assembly.registry_store().put_repo(&record).await.unwrap();
1194
1195        let (pack, tip, _base, _tree, _blob) = chain_pack();
1196        let report = run_push(
1197            "owner/pushed",
1198            &identity,
1199            assembly.registry().clone(),
1200            assembly.refs().clone(),
1201            assembly.packgen().clone(),
1202            assembly.ingest().clone(),
1203            &[create_command("refs/heads/main", tip)],
1204            &pack,
1205        )
1206        .await;
1207        assert_eq!(
1208            report,
1209            vec!["unpack ok".to_owned(), "ok refs/heads/main".to_owned(),],
1210            "the band-1 report equals the capture's shape"
1211        );
1212
1213        let lines = assembly.refs().list_refs(&record).await.unwrap();
1214        assert_eq!(
1215            lines
1216                .iter()
1217                .find(|line| line.name == "refs/heads/main")
1218                .map(|line| line.oid),
1219            Some(Some(tip)),
1220            "the committed ref landed through the entry's refs side"
1221        );
1222        assert!(assembly.refs_version() >= 1, "the CAS bumped the counter");
1223
1224        let events = feed_events(&assembly, 1).await;
1225        assert_eq!(
1226            events,
1227            vec![crate::reactor::RegistryEvent::Mutation {
1228                seq: 2,
1229                repo_id: "owner/pushed".to_owned(),
1230            }],
1231            "the marker write announced post-CAS — the watermark leg"
1232        );
1233        let watermark = assembly.reactor().registry().watermark().await.unwrap();
1234        assert_eq!(watermark, 2, "the put (1) + the push's marker (2)");
1235
1236        let report = assembly.blobs().sweep().await.expect("the sweep runs");
1237        assert!(
1238            report.aborted.is_none() && report.deleted == 0,
1239            "the pushed chain is liveness-covered — the registered closure \
1240             and the committed custody hold it across the sweep"
1241        );
1242    }
1243
1244    /// The unauthorized shape: the ungranted identity reads the generic
1245    /// denial — resolve succeeds (the record exists) and the
1246    /// authorization collapses with the refusal (the unknown ≡
1247    /// unauthorized posture rides the entry's impls untouched).
1248    #[tokio::test(flavor = "multi_thread")]
1249    async fn the_receive_path_enforces_the_visible_surface() {
1250        let assembly = mem_assembly("gate").await;
1251        let identity = identity();
1252        let record = ungranted_record("owner/gated");
1253        assembly.registry_store().put_repo(&record).await.unwrap();
1254
1255        let (_pack, tip, _a, _b, _c) = chain_pack();
1256        let limits = Limits::default();
1257        let (peer, stream) = duplex(1 << 16);
1258        let joined = tokio::spawn({
1259            let registry = assembly.registry().clone();
1260            let refs = assembly.refs().clone();
1261            let packgen = assembly.packgen().clone();
1262            let ingest = assembly.ingest().clone();
1263            let identity = identity.clone();
1264            let pipeline = Arc::new(Semaphore::new(limits.max_concurrent_blocking_tasks));
1265            async move {
1266                serve_duplex_resolved(
1267                    "owner/gated",
1268                    Some(&identity),
1269                    Service::ReceivePack,
1270                    stream,
1271                    limits,
1272                    registry,
1273                    refs,
1274                    packgen,
1275                    ingest,
1276                    false,
1277                    pipeline,
1278                )
1279                .await
1280            }
1281        });
1282
1283        // The pre-protocol gate denies before any protocol byte: the
1284        // client reads the one `ERR <message>` pkt-line and the close
1285        // (no advertisement, no existence oracle — writing first does
1286        // not change the collapse). The push bytes ride their own task
1287        // against the write half because the server's pinned posture is
1288        // deny-then-close without ever reading: it may complete the
1289        // whole session before the client's write drains, and that
1290        // write's `BrokenPipe` is the honest outcome of the losing
1291        // interleave (pinned alongside the drain — never retried,
1292        // never tolerating a third shape).
1293        let chunks = [
1294            pkt(format!("{} {tip} refs/heads/main\0report-status", "0".repeat(40)).as_bytes()),
1295            b"0000".to_vec(),
1296        ];
1297        let (mut peer_read, mut peer_write) = tokio::io::split(peer);
1298        let write_outcome =
1299            tokio::spawn(async move { peer_write.write_all(&chunks.concat()).await })
1300                .await
1301                .expect("the client write task joins");
1302        assert!(
1303            write_outcome.is_ok()
1304                || matches!(
1305                    &write_outcome,
1306                    Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe
1307                ),
1308            "the push bytes drain against a live session, or the \
1309             deny-then-close beat them to the close — never another \
1310             shape: {write_outcome:?}"
1311        );
1312
1313        let denial = {
1314            let mut buf = Vec::new();
1315            peer_read
1316                .read_to_end(&mut buf)
1317                .await
1318                .expect("the denial body reads to the close");
1319            buf
1320        };
1321        assert_eq!(
1322            &denial[4..],
1323            format!("ERR access denied: {}", "owner/gated").as_bytes(),
1324            "the denial is the one wire shape — its message the \
1325             client-requested id and nothing else"
1326        );
1327
1328        drop(peer_read);
1329        let result = joined
1330            .await
1331            .expect("the session task joins")
1332            .expect_err("the denial ends the session denied");
1333        assert!(
1334            matches!(result, SubstrateError::AccessDenied),
1335            "the collapse is the one generic denial: {result:?}"
1336        );
1337        assert!(
1338            assembly.refs().list_refs(&record).await.unwrap().len() <= 1,
1339            "only the seeded HEAD row exists — no ungranted write landed"
1340        );
1341    }
1342
1343    /// The maintenance chain rides the entry's wiring: the worker
1344    /// claims and dispatches the announced mutation's housekeeping job
1345    /// (the enqueue leg's consumer side, observable).
1346    #[tokio::test(flavor = "multi_thread")]
1347    async fn the_chain_rides_the_entry_and_drains_the_housekeeping_job() {
1348        let root = unique_root("chain");
1349        let options = AssemblyOptions {
1350            maintenance: crate::maintenance::MaintenanceOpts {
1351                worker_poll: Duration::from_millis(20),
1352                reconcile_tick: Duration::from_secs(60),
1353                sweep_spec: "@every 1h".to_owned(),
1354                ..Default::default()
1355            },
1356            ..AssemblyOptions::new(root.join("work"), root.join("large"))
1357        };
1358        let assembly = SubstrateAssembly::mem(
1359            Arc::new(alkstore_mem::MemStore::new()),
1360            mem_object_pool(),
1361            options,
1362        )
1363        .await
1364        .expect("the mem assembly composes");
1365
1366        let identity = identity();
1367        let record = granted_record("owner/hk", &identity);
1368        assembly.registry_store().put_repo(&record).await.unwrap();
1369
1370        let seen = Arc::new(parking_lot::Mutex::new(Vec::new()));
1371        let stop = alkstore::StopToken::new();
1372        let worker = {
1373            let seen = seen.clone();
1374            tokio::spawn({
1375                let stop = stop.clone();
1376                let seen_probe = seen.clone();
1377                async move {
1378                    let result = assembly
1379                        .chain()
1380                        .run("entry-worker", stop, move |event| {
1381                            seen_probe.lock().push(event);
1382                        })
1383                        .await;
1384                    result.expect("the chain ran clean");
1385                }
1386            })
1387        };
1388
1389        let deadline = Instant::now() + Duration::from_secs(5);
1390        while Instant::now() < deadline {
1391            if seen
1392                .lock()
1393                .iter()
1394                .any(|event| matches!(event, crate::maintenance::WorkerEvent::PackHousekeeping(_)))
1395            {
1396                break;
1397            }
1398            tokio::time::sleep(Duration::from_millis(20)).await;
1399        }
1400        stop.cancel();
1401
1402        tokio::time::timeout(Duration::from_secs(5), worker)
1403            .await
1404            .expect("the chain wound down inside the deadline")
1405            .expect("the chain task joins clean");
1406
1407        assert!(
1408            seen.lock()
1409                .iter()
1410                .any(|event| matches!(event, crate::maintenance::WorkerEvent::PackHousekeeping(_))),
1411            "the chain drained the announced mutation's housekeeping job"
1412        );
1413    }
1414
1415    /// A store double whose scheduler leg dies instantly — the O-b
1416    /// disposition's probe (the chain's legs wind down via the wired
1417    /// internal token; the run returns the substrate failure
1418    /// promptly).
1419    struct SchedulerDeadStore {
1420        inner: Box<dyn AlkStore>,
1421    }
1422
1423    impl AlkStore for SchedulerDeadStore {
1424        fn begin_tx(
1425            &self,
1426        ) -> alkstore::BoxedFuture<'_, alkstore::Result<Box<dyn alkstore::TxHandle + Send>>>
1427        {
1428            self.inner.begin_tx()
1429        }
1430
1431        fn notify<'a>(
1432            &'a self,
1433            channel: &str,
1434            payload: Value,
1435        ) -> alkstore::BoxedFuture<'a, alkstore::Result<()>> {
1436            self.inner.notify(channel, payload)
1437        }
1438
1439        fn listen<'a>(
1440            &'a self,
1441            channel: &str,
1442        ) -> alkstore::BoxedFuture<'a, alkstore::Result<Box<dyn alkstore::WakeReceiver>>> {
1443            self.inner.listen(channel)
1444        }
1445
1446        fn stream<'a>(
1447            &'a self,
1448            name: &str,
1449        ) -> alkstore::BoxedFuture<'a, alkstore::Result<Box<dyn alkstore::StreamHandle>>> {
1450            self.inner.stream(name)
1451        }
1452
1453        fn queue<'a>(
1454            &'a self,
1455            name: &str,
1456            opts: alkstore::QueueOpts,
1457        ) -> alkstore::BoxedFuture<'a, alkstore::Result<Box<dyn alkstore::Queue>>> {
1458            self.inner.queue(name, opts)
1459        }
1460
1461        fn outbox<'a>(
1462            &'a self,
1463            name: &str,
1464        ) -> alkstore::BoxedFuture<'a, alkstore::Result<Box<dyn alkstore::Outbox>>> {
1465            self.inner.outbox(name)
1466        }
1467
1468        fn try_lock<'a>(
1469            &'a self,
1470            name: &str,
1471            owner: &str,
1472            ttl: i64,
1473        ) -> alkstore::BoxedFuture<'a, alkstore::Result<Option<Box<dyn alkstore::Lock>>>> {
1474            self.inner.try_lock(name, owner, ttl)
1475        }
1476
1477        fn schedule<'a>(
1478            &'a self,
1479            name: &str,
1480            spec: &str,
1481            queue: &str,
1482            payload: Value,
1483            opts: alkstore::ScheduleOpts,
1484        ) -> alkstore::BoxedFuture<'a, alkstore::Result<alkstore::Schedule>> {
1485            self.inner.schedule(name, spec, queue, payload, opts)
1486        }
1487
1488        fn unschedule<'a>(
1489            &'a self,
1490            name: &str,
1491        ) -> alkstore::BoxedFuture<'a, alkstore::Result<bool>> {
1492            self.inner.unschedule(name)
1493        }
1494
1495        fn run_schedules<'a>(
1496            &'a self,
1497            _stop: alkstore::StopToken,
1498        ) -> alkstore::BoxedFuture<'a, alkstore::Result<()>> {
1499            Box::pin(async { Err(alkstore::Error::database("the scheduler leg died (probe)")) })
1500        }
1501    }
1502
1503    /// The O-b disposition: the scheduler leg's substrate failure ends
1504    /// the chain — the run returns the failure promptly (the wake leg
1505    /// wound down by the wired internal token, not by the join's abort
1506    /// window) and the failure is the chain's return.
1507    #[tokio::test(flavor = "multi_thread")]
1508    async fn a_scheduler_leg_failure_ends_the_chain_with_the_legs_wound_down() {
1509        let root = unique_root("ob");
1510        let options = AssemblyOptions::new(root.join("work"), root.join("large"));
1511        let assembly = SubstrateAssembly::mem(
1512            Arc::new(SchedulerDeadStore {
1513                inner: Box::new(alkstore_mem::MemStore::new()),
1514            }) as Arc<dyn AlkStore>,
1515            mem_object_pool(),
1516            options,
1517        )
1518        .await
1519        .expect("the mem assembly composes");
1520
1521        let stop = alkstore::StopToken::new();
1522        let run =
1523            tokio::spawn(async move { assembly.chain().run("ob-worker", stop, |_| {}).await });
1524        let outcome = tokio::time::timeout(Duration::from_millis(750), run)
1525            .await
1526            .expect(
1527                "the chain returned inside the bound — the legs were wound \
1528                     down by the wired token, not outwaited into the join's \
1529                     abort window",
1530            )
1531            .expect("the chain task joins");
1532        let _error = outcome.expect_err("the scheduler leg's failure is the chain's return");
1533    }
1534
1535    /// The through-store serving shape at the entry: the configured
1536    /// `PackGen` puts the generated pack once and serves through the
1537    /// store's ranged windows — the generation through the entry packs
1538    /// exactly the pushed chain's closure (the serving half over the
1539    /// same wiring the push landed its objects on).
1540    #[tokio::test(flavor = "multi_thread")]
1541    async fn the_through_store_serving_shape_composes_at_the_entry() {
1542        let root = unique_root("through-store");
1543        let options = AssemblyOptions {
1544            pack_serving: PackServing::ThroughStore,
1545            ..AssemblyOptions::new(root.join("work"), root.join("large"))
1546        };
1547        let blobs = Arc::new(
1548            alkblobs::Store::dual_tier_mem_local(
1549                root.join("large"),
1550                alkblobs::DEFAULT_DISPATCH_THRESHOLD,
1551            )
1552            .await
1553            .expect("the dual-tier mem object pool constructs"),
1554        );
1555        let assembly =
1556            SubstrateAssembly::mem(Arc::new(alkstore_mem::MemStore::new()), blobs, options)
1557                .await
1558                .expect("the mem assembly composes");
1559
1560        let identity = identity();
1561        let record = granted_record("owner/served", &identity);
1562        assembly.registry_store().put_repo(&record).await.unwrap();
1563        let (pack, tip, base, tree_tip, blob) = chain_pack();
1564        run_push(
1565            "owner/served",
1566            &identity,
1567            assembly.registry().clone(),
1568            assembly.refs().clone(),
1569            assembly.packgen().clone(),
1570            assembly.ingest().clone(),
1571            &[create_command("refs/heads/main", tip)],
1572            &pack,
1573        )
1574        .await;
1575
1576        #[derive(Clone)]
1577        struct SinkBytes(Arc<parking_lot::Mutex<Vec<u8>>>);
1578        impl SinkBytes {
1579            fn bytes(&self) -> Vec<u8> {
1580                self.0.lock().clone()
1581            }
1582        }
1583        impl std::io::Write for SinkBytes {
1584            fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1585                self.0.lock().extend_from_slice(buf);
1586                Ok(buf.len())
1587            }
1588            fn flush(&mut self) -> std::io::Result<()> {
1589                Ok(())
1590            }
1591        }
1592        let sink = SinkBytes(Arc::new(parking_lot::Mutex::new(Vec::new())));
1593        assembly
1594            .packgen()
1595            .generate(
1596                &record,
1597                vec![tip],
1598                Vec::new(),
1599                Limits::default(),
1600                Box::new(sink.clone()),
1601            )
1602            .await
1603            .expect("the fetch generates over the through-store shape");
1604
1605        let pack = sink.bytes();
1606        let mut entries = gix_pack::data::input::BytesToEntriesIter::new_from_header(
1607            std::io::Cursor::new(&pack),
1608            gix_pack::data::input::Mode::Verify,
1609            gix_pack::data::input::EntryDataMode::Keep,
1610            HashKind::Sha1,
1611        )
1612        .expect("the pack stream starts with a decodable header");
1613        let mut ids = Vec::new();
1614        let mut decompressor = gix_zlib::Decompress::new();
1615        for entry in &mut entries {
1616            let entry = entry.expect("every pack entry decodes");
1617            let compressed = entry.compressed.expect("keep mode retains entry bytes");
1618            let mut object = vec![0; entry.decompressed_size as usize];
1619            gix_zlib::stream::inflate::read(
1620                &mut std::io::Cursor::new(compressed),
1621                &mut decompressor,
1622                &mut object,
1623            )
1624            .expect("the entry inflates");
1625            decompressor.reset();
1626            let kind = match entry.header {
1627                PackHeader::Blob => Kind::Blob,
1628                PackHeader::Tree => Kind::Tree,
1629                PackHeader::Commit => Kind::Commit,
1630                PackHeader::Tag => Kind::Tag,
1631                other => panic!("the loose closure packs no deltas: {other:?}"),
1632            };
1633            ids.push(oid_of(kind, &object));
1634        }
1635        let tree_base = oid_of(Kind::Tree, &tree_bytes(&[("a.txt", &blob)]));
1636        let expected = std::collections::BTreeSet::from([tip, base, tree_base, tree_tip, blob]);
1637        assert_eq!(
1638            ids.iter()
1639                .copied()
1640                .collect::<std::collections::BTreeSet<ObjectId>>(),
1641            expected,
1642            "the through-store generation packs exactly the pushed chain's closure"
1643        );
1644
1645        let _ = std::fs::remove_dir_all(&root);
1646    }
1647
1648    // ——— the sqlite shape ———
1649
1650    /// The default assembly composes co-tenant: the receive path
1651    /// composes end-to-end through it (resolve → authorize → serve →
1652    /// commit side → ingest), the legs observable on the durable feed,
1653    /// the seeded HEAD rides the entry's refs side, and the pushed
1654    /// chain is liveness-covered across a sweep. (Reopen/persistence is
1655    /// the twin assembly's own probe; the entry pins the composed legs.)
1656    #[cfg(feature = "sqlite")]
1657    #[tokio::test(flavor = "multi_thread")]
1658    async fn the_sqlite_assembly_composes_default_and_carries_the_receive_path() {
1659        let root = unique_root("sqlite");
1660        std::fs::create_dir_all(&root).unwrap();
1661        let options = AssemblyOptions::new(root.join("work"), root.join("large"));
1662        let assembly = SubstrateAssembly::sqlite(root.join("co-tenant.db"), options)
1663            .await
1664            .expect("the default assembly composes");
1665        let identity = identity();
1666        let record = granted_record("owner/composed-sqlite", &identity);
1667        assembly.registry_store().put_repo(&record).await.unwrap();
1668
1669        let events = feed_events(&assembly, 0).await;
1670        assert_eq!(
1671            events.len(),
1672            1,
1673            "the announced put fired through the twin assembly the entry unified"
1674        );
1675
1676        let (pack, tip, _base, _tree, _blob) = chain_pack();
1677        let report = run_push(
1678            "owner/composed-sqlite",
1679            &identity,
1680            assembly.registry().clone(),
1681            assembly.refs().clone(),
1682            assembly.packgen().clone(),
1683            assembly.ingest().clone(),
1684            &[create_command("refs/heads/main", tip)],
1685            &pack,
1686        )
1687        .await;
1688        assert_eq!(
1689            report,
1690            vec!["unpack ok".to_owned(), "ok refs/heads/main".to_owned()]
1691        );
1692
1693        let lines = assembly.refs().list_refs(&record).await.unwrap();
1694        assert_eq!(
1695            lines
1696                .iter()
1697                .find(|line| line.name == "refs/heads/main")
1698                .map(|line| line.oid),
1699            Some(Some(tip))
1700        );
1701
1702        let events = feed_events(&assembly, 1).await;
1703        assert_eq!(
1704            events,
1705            vec![crate::reactor::RegistryEvent::Mutation {
1706                seq: 2,
1707                repo_id: "owner/composed-sqlite".to_owned(),
1708            }],
1709            "the marker + announce legs rode the committed CAS"
1710        );
1711
1712        let report = assembly.blobs().sweep().await.expect("the sweep runs");
1713        assert!(
1714            report.aborted.is_none() && report.deleted == 0,
1715            "the pushed chain is covered (the seeded closure over the co-tenant rows)"
1716        );
1717
1718        let _ = std::fs::remove_dir_all(&root);
1719    }
1720
1721    /// DQ-7 by construction at the entry: `:memory:` never reaches an
1722    /// engine — the sqlite entry refuses it before the twin assembly
1723    /// opens.
1724    #[cfg(feature = "sqlite")]
1725    #[tokio::test]
1726    async fn the_sqlite_entry_refuses_memory_mode() {
1727        let root = unique_root("dq7");
1728        let options = AssemblyOptions::new(root.join("work"), root.join("large"));
1729        let outcome = SubstrateAssembly::sqlite(":memory:", options).await;
1730        assert!(matches!(outcome, Err(OpenError::Invalid(_))));
1731    }
1732
1733    /// The sqlite shape's LFS row store through the entry (the `lfs`
1734    /// accessor): rows asserted through the composition surface, the
1735    /// shared oid's refcounts carried, and the record-transaction
1736    /// removal leg — the rows go with the repo through
1737    /// `registry_store` (the twin's unconditional in-SQL copy). The
1738    /// mem shape's leg drains [`crate::lfs::InMemoryLfsRows`] (the
1739    /// mem test below); the pg shape runs the same script on the live
1740    /// harness (the `live` module below).
1741    #[cfg(all(feature = "lfs", feature = "sqlite"))]
1742    #[tokio::test(flavor = "multi_thread")]
1743    async fn the_sqlite_row_store_asserts_and_the_rows_leave_with_the_repo() {
1744        let root = unique_root("lfs-rows");
1745        std::fs::create_dir_all(&root).unwrap();
1746        let options = AssemblyOptions::new(root.join("work"), root.join("large"));
1747        let assembly = SubstrateAssembly::sqlite(root.join("co-tenant.db"), options)
1748            .await
1749            .expect("the default assembly composes");
1750        run_lfs_rows_removal_leg(&assembly).await;
1751        let _ = std::fs::remove_dir_all(&root);
1752    }
1753
1754    #[cfg(all(feature = "lfs", not(feature = "sqlite")))]
1755    #[tokio::test(flavor = "multi_thread")]
1756    async fn the_mem_row_store_asserts_and_the_rows_leave_with_the_repo() {
1757        let assembly = mem_assembly("lfs-rows").await;
1758        run_lfs_rows_removal_leg(&assembly).await;
1759    }
1760
1761    #[cfg(all(feature = "lfs", feature = "sqlite"))]
1762    #[tokio::test(flavor = "multi_thread")]
1763    async fn the_mem_row_store_asserts_and_the_rows_leave_with_the_repo() {
1764        let assembly = mem_assembly("lfs-rows").await;
1765        run_lfs_rows_removal_leg(&assembly).await;
1766    }
1767
1768    /// The leg's script shared by the mem and sqlite shapes: two
1769    /// repos, one shared oid (refcount 2 on both rows), repo deletion
1770    /// through `registry_store` — the victim's rows gone, the
1771    /// survivor's row recomputed.
1772    #[cfg(feature = "lfs")]
1773    async fn run_lfs_rows_removal_leg(assembly: &SubstrateAssembly) {
1774        let identity = identity();
1775        assembly
1776            .registry_store()
1777            .put_repo(&granted_record("owner/victim-lfs", &identity))
1778            .await
1779            .unwrap();
1780        assembly
1781            .registry_store()
1782            .put_repo(&granted_record("owner/survivor-lfs", &identity))
1783            .await
1784            .unwrap();
1785
1786        let shared_oid = "01".repeat(32);
1787        assembly
1788            .lfs_rows()
1789            .assert("owner/victim-lfs", &shared_oid, 16)
1790            .await
1791            .unwrap();
1792        assembly
1793            .lfs_rows()
1794            .assert("owner/survivor-lfs", &shared_oid, 16)
1795            .await
1796            .unwrap();
1797        let rows = assembly.lfs_rows().rows().await.unwrap();
1798        assert_eq!(rows.len(), 2);
1799        for row in &rows {
1800            assert_eq!(row.refcount, 2);
1801        }
1802
1803        assembly
1804            .registry_store()
1805            .remove_repo("owner/victim-lfs")
1806            .await
1807            .unwrap();
1808        let rows = assembly.lfs_rows().rows().await.unwrap();
1809        assert_eq!(
1810            rows,
1811            vec![crate::lfs::LfsRow {
1812                repo_id: "owner/survivor-lfs".to_owned(),
1813                oid: shared_oid,
1814                size: 16,
1815                refcount: 1,
1816            }],
1817            "the victim's rows went with the repo; the survivor carries the recomputed count"
1818        );
1819    }
1820
1821    // ——— the LFS liveness source's sweep contract, entry-wired ———
1822
1823    /// Put one LFS media entry under its own family (the facade seam's
1824    /// put — the media family the liveness source marks) and read its
1825    /// oid back off the derived key: the hex the row store asserts by.
1826    #[cfg(feature = "lfs")]
1827    async fn put_media(
1828        assembly: &SubstrateAssembly,
1829        content: &'static [u8],
1830    ) -> (String, alkblobs::Key) {
1831        let (key, _pin) = assembly
1832            .blobs()
1833            .put_as(
1834                alkblobs::Algorithm::GitObjectSha256,
1835                Some(content.len() as u64),
1836                std::io::Cursor::new(content),
1837            )
1838            .await
1839            .expect("the media put lands");
1840        let hex: String = key
1841            .digest()
1842            .bytes()
1843            .iter()
1844            .map(|b| format!("{b:02x}"))
1845            .collect();
1846        (hex, key)
1847    }
1848
1849    #[cfg(feature = "lfs")]
1850    async fn pool_has(assembly: &SubstrateAssembly, key: &alkblobs::Key) -> bool {
1851        assembly
1852            .blobs()
1853            .stat(&key.as_bytes())
1854            .await
1855            .expect("the pool probe reads")
1856            .is_some()
1857    }
1858
1859    /// The sweep contract through the mem entry (the `lfs` source's
1860    /// fail-safe registration observable: the re-protected media never
1861    /// stages — an unregistered source's missed mark would stage it,
1862    /// the dangerous default ADR-022 §6 pins against). Cross-reference
1863    /// re-protection (one key, two rows, removed one by one across
1864    /// sweeps), orphan reclaim (the never-asserted media), and the
1865    /// repo-deletion reclaim (the rows leave with the repo through the
1866    /// record-removal leg; the media sweeps — nothing LFS-specific).
1867    #[cfg(feature = "lfs")]
1868    #[tokio::test(flavor = "multi_thread")]
1869    async fn the_mem_entrys_lfs_source_reprotects_and_reclaims_media() {
1870        let assembly = mem_assembly("lfs-gc").await;
1871        let _token = assembly.lfs_registration();
1872
1873        let (shared_oid, shared_key) = put_media(&assembly, b"lfs media shared\n").await;
1874        let (_, orphan_key) = put_media(&assembly, b"lfs media orphan\n").await;
1875        let identity = identity();
1876        assembly
1877            .registry_store()
1878            .put_repo(&granted_record("owner/repro-1", &identity))
1879            .await
1880            .unwrap();
1881        assembly
1882            .registry_store()
1883            .put_repo(&granted_record("owner/repro-2", &identity))
1884            .await
1885            .unwrap();
1886        assembly
1887            .lfs_rows()
1888            .assert("owner/repro-1", &shared_oid, 16)
1889            .await
1890            .unwrap();
1891        assembly
1892            .lfs_rows()
1893            .assert("owner/repro-2", &shared_oid, 16)
1894            .await
1895            .unwrap();
1896
1897        let first = assembly.blobs().sweep().await.expect("the sweep runs");
1898        assert!(first.aborted.is_none());
1899        assert_eq!(
1900            first.candidates_staged, 1,
1901            "only the never-asserted orphan marks uncovered"
1902        );
1903        assert_eq!(first.deleted, 1, "the orphan reclaims (POC-L's datum)");
1904        assert!(pool_has(&assembly, &shared_key).await, "referenced survive");
1905        assert!(
1906            !pool_has(&assembly, &orphan_key).await,
1907            "the orphan is gone from the pool"
1908        );
1909
1910        assembly
1911            .lfs_rows()
1912            .remove("owner/repro-1", &shared_oid)
1913            .await
1914            .unwrap();
1915        let second = assembly.blobs().sweep().await.expect("the sweep runs");
1916        assert!(
1917            second.deleted == 0 && second.candidates_staged == 0,
1918            "one row removed — the key stays protected while any row remains"
1919        );
1920        assert!(pool_has(&assembly, &shared_key).await);
1921
1922        assembly
1923            .lfs_rows()
1924            .remove("owner/repro-2", &shared_oid)
1925            .await
1926            .unwrap();
1927        let third = assembly.blobs().sweep().await.expect("the sweep runs");
1928        assert_eq!(
1929            third.deleted, 1,
1930            "the last row's removal makes the media sweep-eligible"
1931        );
1932        assert!(!pool_has(&assembly, &shared_key).await);
1933
1934        let (victim_oid, victim_key) = put_media(&assembly, b"lfs victim media\n").await;
1935        assembly
1936            .registry_store()
1937            .put_repo(&granted_record("owner/victim-lfs", &identity))
1938            .await
1939            .unwrap();
1940        assembly
1941            .lfs_rows()
1942            .assert("owner/victim-lfs", &victim_oid, 16)
1943            .await
1944            .unwrap();
1945        let fourth = assembly.blobs().sweep().await.expect("the sweep runs");
1946        assert!(
1947            fourth.deleted == 0 && fourth.candidates_staged == 0,
1948            "the row stands — the media re-protects"
1949        );
1950        assembly
1951            .registry_store()
1952            .remove_repo("owner/victim-lfs")
1953            .await
1954            .unwrap();
1955        assert!(
1956            assembly
1957                .lfs_rows()
1958                .repo_rows("owner/victim-lfs")
1959                .await
1960                .unwrap()
1961                .is_empty(),
1962            "the rows went with the repo (the record-removal leg)"
1963        );
1964        let fifth = assembly.blobs().sweep().await.expect("the sweep runs");
1965        assert_eq!(
1966            fifth.deleted, 1,
1967            "the repo-deletion reclaim — nothing LFS-specific (lfs.md §3)"
1968        );
1969        assert!(!pool_has(&assembly, &victim_key).await);
1970    }
1971
1972    /// The same contract's sqlite-entry rows (the default assembly
1973    /// shape's fail-safe registration observable): the re-protected
1974    /// media never stages, the uncovering marks it into the durable
1975    /// window (the shipped window delay defers the reclaim — the
1976    /// stage-only observable at the entry; the full stage→
1977    /// executed-overdue timing runs on the zero-delay wiring in
1978    /// [`crate::liveness::media_tests`]).
1979    #[cfg(all(feature = "lfs", feature = "sqlite"))]
1980    #[tokio::test(flavor = "multi_thread")]
1981    async fn the_sqlite_entrys_lfs_source_reprotects_media_across_the_sweep() {
1982        let root = unique_root("lfs-gc");
1983        std::fs::create_dir_all(&root).unwrap();
1984        let options = AssemblyOptions::new(root.join("work"), root.join("large"));
1985        let assembly = SubstrateAssembly::sqlite(root.join("co-tenant.db"), options)
1986            .await
1987            .expect("the default assembly composes");
1988
1989        let (shared_oid, shared_key) = put_media(&assembly, b"lfs media shared\n").await;
1990        let identity = identity();
1991        assembly
1992            .registry_store()
1993            .put_repo(&granted_record("owner/repro-1", &identity))
1994            .await
1995            .unwrap();
1996        assembly
1997            .registry_store()
1998            .put_repo(&granted_record("owner/repro-2", &identity))
1999            .await
2000            .unwrap();
2001        assembly
2002            .lfs_rows()
2003            .assert("owner/repro-1", &shared_oid, 16)
2004            .await
2005            .unwrap();
2006        assembly
2007            .lfs_rows()
2008            .assert("owner/repro-2", &shared_oid, 16)
2009            .await
2010            .unwrap();
2011
2012        let first = assembly.blobs().sweep().await.expect("the sweep runs");
2013        assert!(first.aborted.is_none());
2014        assert_eq!(
2015            first.candidates_staged, 0,
2016            "the rows re-protect the media — the registered source's mark consult"
2017        );
2018        assert_eq!(first.deleted, 0);
2019
2020        assembly
2021            .lfs_rows()
2022            .remove("owner/repro-1", &shared_oid)
2023            .await
2024            .unwrap();
2025        let second = assembly.blobs().sweep().await.expect("the sweep runs");
2026        assert!(
2027            second.deleted == 0 && second.candidates_staged == 0,
2028            "one row removed — the survivor re-protects across the durable window"
2029        );
2030        assert!(pool_has(&assembly, &shared_key).await);
2031
2032        assembly
2033            .lfs_rows()
2034            .remove("owner/repro-2", &shared_oid)
2035            .await
2036            .unwrap();
2037        let third = assembly.blobs().sweep().await.expect("the sweep runs");
2038        assert_eq!(
2039            third.candidates_staged, 1,
2040            "the last row's removal marks the media — staged into the window"
2041        );
2042        assert_eq!(third.deleted, 0, "the window defers the reclaim");
2043        assert!(pool_has(&assembly, &shared_key).await);
2044
2045        let _ = std::fs::remove_dir_all(&root);
2046    }
2047
2048    // ——— the postgres shape (the live-pg harness) ———
2049
2050    #[cfg(feature = "postgres")]
2051    mod live {
2052        use super::*;
2053        use crate::postgres::live_harness::{drop_schema, harness_dsn, unique_schema, LIVE_GATE};
2054
2055        /// The fleet/durable-shared assembly composes over the harness
2056        /// container: the receive path composes end-to-end, the legs
2057        /// observable on the durable feed, and the object pool rides
2058        /// the shared DSN.
2059        ///
2060        /// The live gate serializes the announced writes against the
2061        /// harness's database-wide silence-assertion probe (the
2062        /// `LISTEN/NOTIFY` channel is per-database, not per-schema —
2063        /// [`crate::postgres::live_harness::LIVE_GATE`]).
2064        #[tokio::test(flavor = "multi_thread")]
2065        async fn the_postgres_assembly_composes_and_carries_the_receive_path() {
2066            let Some(dsn) = harness_dsn() else {
2067                eprintln!(
2068                    "skipping (no postgres harness server): set \
2069                     ALKGIT_SUBSTRATE_PG_HOST (or the ALKBLOBS_PG_* harness env)"
2070                );
2071                return;
2072            };
2073            let _gate = LIVE_GATE.lock().await;
2074            let schema = unique_schema("compose");
2075            let registry = crate::postgres::PgRegistryOptions {
2076                schema: schema.clone(),
2077                engine: alkstore_postgres::PgOpts {
2078                    schema: format!("{schema}_engine"),
2079                    ..alkstore_postgres::PgOpts::default()
2080                },
2081                blobs: alkblobs::PgOpts {
2082                    schema: format!("{schema}_blobs"),
2083                    ..alkblobs::PgOpts::default()
2084                },
2085            };
2086            let root = unique_root("pg-compose");
2087            let options = AssemblyOptions::new(root.join("work"), root.join("large"));
2088            let assembly = match SubstrateAssembly::postgres(&dsn, registry.clone(), options).await
2089            {
2090                Ok(assembly) => assembly,
2091                Err(error) => {
2092                    drop_schema(&dsn, &schema).await;
2093                    drop_schema(&dsn, &format!("{}_engine", schema)).await;
2094                    drop_schema(&dsn, &format!("{}_blobs", schema)).await;
2095                    panic!("the pg assembly composes on the harness: {error}");
2096                }
2097            };
2098
2099            let identity = identity();
2100            let record = granted_record("owner/composed-pg", &identity);
2101            assembly.registry_store().put_repo(&record).await.unwrap();
2102            assert_eq!(
2103                assembly
2104                    .registry()
2105                    .resolve("owner/composed-pg")
2106                    .await
2107                    .unwrap(),
2108                record,
2109                "the provider carried the fleet record"
2110            );
2111
2112            let (pack, tip, _base, _tree, _blob) = chain_pack();
2113            let report = run_push(
2114                "owner/composed-pg",
2115                &identity,
2116                assembly.registry().clone(),
2117                assembly.refs().clone(),
2118                assembly.packgen().clone(),
2119                assembly.ingest().clone(),
2120                &[create_command("refs/heads/main", tip)],
2121                &pack,
2122            )
2123            .await;
2124            assert_eq!(
2125                report,
2126                vec!["unpack ok".to_owned(), "ok refs/heads/main".to_owned()]
2127            );
2128
2129            let lines = assembly.refs().list_refs(&record).await.unwrap();
2130            assert_eq!(
2131                lines
2132                    .iter()
2133                    .find(|line| line.name == "refs/heads/main")
2134                    .map(|line| line.oid),
2135                Some(Some(tip))
2136            );
2137
2138            let events = feed_events(&assembly, 1).await;
2139            assert_eq!(
2140                events,
2141                vec![crate::reactor::RegistryEvent::Mutation {
2142                    seq: 2,
2143                    repo_id: "owner/composed-pg".to_owned(),
2144                }],
2145                "the marker + announce legs rode the committed CAS through the \
2146                 fleet assembly"
2147            );
2148
2149            let report = assembly.blobs().sweep().await.expect("the sweep runs");
2150            assert!(
2151                report.aborted.is_none() && report.deleted == 0,
2152                "the pushed chain is covered (the in-process closure source over \
2153                 the co-tenant pg rows)"
2154            );
2155
2156            drop(assembly);
2157            drop_schema(&dsn, &schema).await;
2158            drop_schema(&dsn, &format!("{}_engine", schema)).await;
2159            drop_schema(&dsn, &format!("{}_blobs", schema)).await;
2160            let _ = std::fs::remove_dir_all(&root);
2161        }
2162
2163        /// The fleet liveness answer's materialization exercise (wave
2164        /// 7's O-a booking, riding wave 10): the shipped pg entry
2165        /// pushes a real chain; the pushed chain's git keys ride the
2166        /// embedder's materialized liveness table beside the
2167        /// co-tenant tables (the composition's fleet-wide answer); a
2168        /// second instance — a kv-postgres **fleet** store over the
2169        /// same DSN, **no in-process liveness source** — registers the
2170        /// `FleetTable` reference and the store core reads it at the
2171        /// delete-time SQL arbitration. Pinned: the mark stages every
2172        /// pool candidate (the table is never enumerated); the
2173        /// materialized key cancels at the re-arbitration while the
2174        /// unmaterialized keys reclaim at the same execution; once the
2175        /// table row leaves, the last protected key reclaims too (the
2176        /// pool empties); and the fleet form errors on the entry's own
2177        /// store (the non-fleet boundary — the honest surface the
2178        /// entry ships). The wiring inputs this completes the booking
2179        /// with are the composition-adjacent materialization itself
2180        /// (the wave-8 entry's pg refs legs are its in-process half,
2181        /// consumed by this row's push through the entry).
2182        #[tokio::test(flavor = "multi_thread")]
2183        async fn the_fleet_table_materialization_serves_the_shared_arbitration() {
2184            use std::time::Duration;
2185
2186            use alkblobs::LivenessRegistration;
2187
2188            use crate::odb::oid_key;
2189            use crate::postgres::live_harness::connect_direct;
2190
2191            let Some(dsn) = harness_dsn() else {
2192                eprintln!(
2193                    "skipping (no postgres harness server): set \
2194                     ALKGIT_SUBSTRATE_PG_HOST (or the ALKBLOBS_PG_* harness env)"
2195                );
2196                return;
2197            };
2198            let _gate = LIVE_GATE.lock().await;
2199            let schema = unique_schema("compose-fleet-table");
2200            let registry = crate::postgres::PgRegistryOptions {
2201                schema: schema.clone(),
2202                engine: alkstore_postgres::PgOpts {
2203                    schema: format!("{schema}_engine"),
2204                    ..alkstore_postgres::PgOpts::default()
2205                },
2206                blobs: alkblobs::PgOpts {
2207                    schema: format!("{schema}_blobs"),
2208                    ..alkblobs::PgOpts::default()
2209                },
2210            };
2211            let root = unique_root("pg-fleet-table");
2212            let options = AssemblyOptions::new(root.join("work"), root.join("large"));
2213            let assembly = match SubstrateAssembly::postgres(&dsn, registry.clone(), options).await
2214            {
2215                Ok(assembly) => assembly,
2216                Err(error) => {
2217                    drop_schema(&dsn, &schema).await;
2218                    drop_schema(&dsn, &format!("{}_engine", schema)).await;
2219                    drop_schema(&dsn, &format!("{}_blobs", schema)).await;
2220                    panic!("the pg assembly composes on the harness: {error}");
2221                }
2222            };
2223
2224            // The real push through the entry: the pool carries the
2225            // chain's five git objects.
2226            let identity = identity();
2227            let record = granted_record("owner/fleet-liveness", &identity);
2228            assembly.registry_store().put_repo(&record).await.unwrap();
2229            let (pack, tip, base, tree, blob) = chain_pack();
2230            let report = run_push(
2231                "owner/fleet-liveness",
2232                &identity,
2233                assembly.registry().clone(),
2234                assembly.refs().clone(),
2235                assembly.packgen().clone(),
2236                assembly.ingest().clone(),
2237                &[create_command("refs/heads/main", tip)],
2238                &pack,
2239            )
2240            .await;
2241            assert_eq!(
2242                report,
2243                vec!["unpack ok".to_owned(), "ok refs/heads/main".to_owned()]
2244            );
2245
2246            let chain_keys: Vec<alkblobs::Key> = [&tip, &base, &tree, &blob]
2247                .iter()
2248                .map(|oid| oid_key(oid).expect("the git-object key forms"))
2249                .collect();
2250            for key in &chain_keys {
2251                assert!(
2252                    assembly
2253                        .blobs()
2254                        .stat(key.key_bytes().as_slice())
2255                        .await
2256                        .expect("the pool stats")
2257                        .is_some(),
2258                    "the pushed chain's object sits in the shared pool under its oid key"
2259                );
2260            }
2261            let pool = assembly.blobs().list().await.expect("the pool lists");
2262            let pool_len = pool.len() as u64;
2263            assert_eq!(
2264                pool_len, 5,
2265                "the pushed chain is the pool's whole inventory: {pool:?}"
2266            );
2267
2268            // The embedder's materialized liveness table beside the
2269            // co-tenant tables — the store never owns it (the
2270            // schema-ownership rule); the composition's materializer
2271            // writes it (the row carries the composition's half: the
2272            // one pushed chain's keys, the tip commit materialized).
2273            let probe = connect_direct(&dsn).await;
2274            probe
2275                .batch_execute(&format!(
2276                    "CREATE TABLE \"{schema}\".git_live_keys (key bytea PRIMARY KEY)"
2277                ))
2278                .await
2279                .expect("the embedder table creates");
2280            let tip_key = oid_key(&tip).expect("the tip's key forms");
2281            probe
2282                .execute(
2283                    &format!("INSERT INTO \"{schema}\".git_live_keys (key) VALUES ($1)"),
2284                    &[&tip_key.key_bytes().as_slice()],
2285                )
2286                .await
2287                .expect("the tip's key materializes");
2288
2289            // The second instance: the fleet store over the same DSN
2290            // (one pool), the fleet table its only liveness
2291            // registration — the fleet-wide answer is the table's, no
2292            // in-process sources ride this instance.
2293            let fleet = alkblobs::Store::kv_only_postgres_fleet(
2294                &dsn,
2295                alkblobs::PgOpts {
2296                    schema: format!("{schema}_blobs"),
2297                    ..alkblobs::PgOpts::default()
2298                },
2299                1024 * 1024,
2300                Duration::ZERO,
2301                Duration::from_secs(60),
2302            )
2303            .await
2304            .expect("the fleet store constructs");
2305            let registration = fleet
2306                .register_liveness_source(LivenessRegistration::FleetTable(format!(
2307                    "{schema}.git_live_keys"
2308                )))
2309                .expect("the embedder table reference registers");
2310            assert!(
2311                assembly
2312                    .blobs()
2313                    .register_liveness_source(LivenessRegistration::FleetTable(
2314                        "some.where".to_owned()
2315                    ))
2316                    .is_err(),
2317                "the fleet form errors on the non-fleet entry store (the form boundary)"
2318            );
2319
2320            // Sweep 1: the mark stages every pool candidate; the table
2321            // is never enumerated; nothing executes.
2322            let first = fleet.sweep().await.expect("the sweep runs");
2323            assert_eq!(
2324                first.aborted, None,
2325                "a fleet-table-only registration admits the sweep"
2326            );
2327            assert_eq!(
2328                first.candidates_staged, pool_len,
2329                "the mark stages the chain — the table is never enumerated"
2330            );
2331            assert_eq!(first.deleted, 0, "nothing overdue at the staging mark");
2332
2333            // Sweep 2: the materialized key cancels at the delete-time
2334            // SQL re-arbitration; the unmaterialized keys reclaim at
2335            // the same execution — the protect/reclaim split is the
2336            // table's content.
2337            let second = fleet.sweep().await.expect("the sweep executes");
2338            assert_eq!(second.aborted, None);
2339            assert_eq!(
2340                second.cancelled_by_arbitration, 1,
2341                "the materialized key cancelled at the SQL re-arbitration"
2342            );
2343            assert_eq!(
2344                second.deleted,
2345                pool_len - 1,
2346                "the unmaterialized keys reclaimed at the same execution"
2347            );
2348            assert!(
2349                assembly
2350                    .blobs()
2351                    .stat(tip_key.key_bytes().as_slice())
2352                    .await
2353                    .expect("the pool stats")
2354                    .is_some(),
2355                "the protected key rides the table's protection"
2356            );
2357
2358            // The table row leaves: the last protected key reclaims.
2359            probe
2360                .batch_execute(&format!("DELETE FROM \"{schema}\".git_live_keys"))
2361                .await
2362                .expect("the materializer's removal");
2363            let third = fleet.sweep().await.expect("the sweep executes");
2364            assert_eq!(third.aborted, None);
2365            assert_eq!(
2366                (third.deleted, third.cancelled_by_arbitration),
2367                (1, 0),
2368                "the unprotected key reclaims once its table row left"
2369            );
2370            for key in &chain_keys {
2371                assert!(
2372                    assembly
2373                        .blobs()
2374                        .stat(key.key_bytes().as_slice())
2375                        .await
2376                        .expect("the pool stats")
2377                        .is_none(),
2378                    "the pool emptied once the materialization left"
2379                );
2380            }
2381            let _ = fleet.unregister_liveness_source(registration);
2382
2383            drop(assembly);
2384            drop(probe);
2385            drop_schema(&dsn, &schema).await;
2386            drop_schema(&dsn, &format!("{}_engine", schema)).await;
2387            drop_schema(&dsn, &format!("{}_blobs", schema)).await;
2388            let _ = std::fs::remove_dir_all(&root);
2389        }
2390
2391        /// The pg shape's LFS row store through the entry: the removal
2392        /// leg's script over the harness (the mem/sqlite shapes' script,
2393        /// engine-swapped — the rows go with the repo inside the
2394        /// record-removal transaction).
2395        #[cfg(feature = "lfs")]
2396        #[tokio::test(flavor = "multi_thread")]
2397        async fn the_postgres_row_store_asserts_and_the_rows_leave_with_the_repo() {
2398            let Some(dsn) = harness_dsn() else {
2399                eprintln!(
2400                    "skipping (no postgres harness server): set \
2401                     ALKGIT_SUBSTRATE_PG_HOST (or the ALKBLOBS_PG_* harness env)"
2402                );
2403                return;
2404            };
2405            let _gate = LIVE_GATE.lock().await;
2406            let schema = unique_schema("compose-lfs-rows");
2407            let registry = crate::postgres::PgRegistryOptions {
2408                schema: schema.clone(),
2409                engine: alkstore_postgres::PgOpts {
2410                    schema: format!("{schema}_engine"),
2411                    ..alkstore_postgres::PgOpts::default()
2412                },
2413                blobs: alkblobs::PgOpts {
2414                    schema: format!("{schema}_blobs"),
2415                    ..alkblobs::PgOpts::default()
2416                },
2417            };
2418            let root = unique_root("pg-compose-lfs");
2419            let options = AssemblyOptions::new(root.join("work"), root.join("large"));
2420            let assembly = match SubstrateAssembly::postgres(&dsn, registry.clone(), options).await
2421            {
2422                Ok(assembly) => assembly,
2423                Err(error) => {
2424                    drop_schema(&dsn, &schema).await;
2425                    drop_schema(&dsn, &format!("{}_engine", schema)).await;
2426                    drop_schema(&dsn, &format!("{}_blobs", schema)).await;
2427                    panic!("the pg assembly composes on the harness: {error}");
2428                }
2429            };
2430
2431            let identity = identity();
2432            assembly
2433                .registry_store()
2434                .put_repo(&granted_record("owner/victim-lfs", &identity))
2435                .await
2436                .unwrap();
2437            assembly
2438                .registry_store()
2439                .put_repo(&granted_record("owner/survivor-lfs", &identity))
2440                .await
2441                .unwrap();
2442
2443            let shared_oid = "01".repeat(32);
2444            assembly
2445                .lfs_rows()
2446                .assert("owner/victim-lfs", &shared_oid, 16)
2447                .await
2448                .unwrap();
2449            assembly
2450                .lfs_rows()
2451                .assert("owner/survivor-lfs", &shared_oid, 16)
2452                .await
2453                .unwrap();
2454
2455            assembly
2456                .registry_store()
2457                .remove_repo("owner/victim-lfs")
2458                .await
2459                .unwrap();
2460            assert_eq!(
2461                assembly.lfs_rows().rows().await.unwrap(),
2462                vec![crate::lfs::LfsRow {
2463                    repo_id: "owner/survivor-lfs".to_owned(),
2464                    oid: shared_oid,
2465                    size: 16,
2466                    refcount: 1,
2467                }],
2468                "the victim's rows went with the repo; the survivor carries the recomputed count"
2469            );
2470
2471            drop(assembly);
2472            drop_schema(&dsn, &schema).await;
2473            drop_schema(&dsn, &format!("{}_engine", schema)).await;
2474            drop_schema(&dsn, &format!("{}_blobs", schema)).await;
2475            let _ = std::fs::remove_dir_all(&root);
2476        }
2477
2478        /// The LFS liveness source's sweep-contract row on the live
2479        /// harness (the entry wiring's third shape): the re-protected
2480        /// media never stages while any row stands — cross-reference
2481        /// re-protection through one removed row — and the
2482        /// repo-deletion's uncovering marks the media into the
2483        /// durable window (the shipped window delay defers the
2484        /// reclaim; the full stage→executed-overdue timing is the
2485        /// zero-delay wiring's, [`crate::liveness::media_tests`]).
2486        #[cfg(feature = "lfs")]
2487        #[tokio::test(flavor = "multi_thread")]
2488        async fn the_postgres_entrys_lfs_source_reprotects_media_across_the_sweep() {
2489            let Some(dsn) = harness_dsn() else {
2490                eprintln!(
2491                    "skipping (no postgres harness server): set \
2492                     ALKGIT_SUBSTRATE_PG_HOST (or the ALKBLOBS_PG_* harness env)"
2493                );
2494                return;
2495            };
2496            let _gate = LIVE_GATE.lock().await;
2497            let schema = unique_schema("compose-lfs-gc");
2498            let registry = crate::postgres::PgRegistryOptions {
2499                schema: schema.clone(),
2500                engine: alkstore_postgres::PgOpts {
2501                    schema: format!("{schema}_engine"),
2502                    ..alkstore_postgres::PgOpts::default()
2503                },
2504                blobs: alkblobs::PgOpts {
2505                    schema: format!("{schema}_blobs"),
2506                    ..alkblobs::PgOpts::default()
2507                },
2508            };
2509            let root = unique_root("pg-compose-lfs-gc");
2510            let options = AssemblyOptions::new(root.join("work"), root.join("large"));
2511            let assembly = match SubstrateAssembly::postgres(&dsn, registry.clone(), options).await
2512            {
2513                Ok(assembly) => assembly,
2514                Err(error) => {
2515                    drop_schema(&dsn, &schema).await;
2516                    drop_schema(&dsn, &format!("{}_engine", schema)).await;
2517                    drop_schema(&dsn, &format!("{}_blobs", schema)).await;
2518                    panic!("the pg assembly composes on the harness: {error}");
2519                }
2520            };
2521
2522            let (shared_oid, shared_key) = put_media(&assembly, b"lfs media shared\n").await;
2523            let identity = identity();
2524            assembly
2525                .registry_store()
2526                .put_repo(&granted_record("owner/lfs-repro-1", &identity))
2527                .await
2528                .unwrap();
2529            assembly
2530                .registry_store()
2531                .put_repo(&granted_record("owner/lfs-repro-2", &identity))
2532                .await
2533                .unwrap();
2534            assembly
2535                .lfs_rows()
2536                .assert("owner/lfs-repro-1", &shared_oid, 16)
2537                .await
2538                .unwrap();
2539            assembly
2540                .lfs_rows()
2541                .assert("owner/lfs-repro-2", &shared_oid, 16)
2542                .await
2543                .unwrap();
2544
2545            let first = assembly.blobs().sweep().await.expect("the sweep runs");
2546            assert!(
2547                first.aborted.is_none() && first.candidates_staged == 0 && first.deleted == 0,
2548                "the rows re-protect the media — the registered source consulted the \
2549                 co-tenant pg rows"
2550            );
2551            assert!(pool_has(&assembly, &shared_key).await);
2552
2553            assembly
2554                .lfs_rows()
2555                .remove("owner/lfs-repro-1", &shared_oid)
2556                .await
2557                .unwrap();
2558            let second = assembly.blobs().sweep().await.expect("the sweep runs");
2559            assert!(
2560                second.deleted == 0 && second.candidates_staged == 0,
2561                "one row removed — the survivor re-protects across the durable window"
2562            );
2563
2564            assembly
2565                .registry_store()
2566                .remove_repo("owner/lfs-repro-2")
2567                .await
2568                .unwrap();
2569            let third = assembly.blobs().sweep().await.expect("the sweep runs");
2570            assert_eq!(
2571                third.candidates_staged, 1,
2572                "the rows all left with the repo — the mark uncovered the media"
2573            );
2574            assert_eq!(third.deleted, 0, "the durable window defers the reclaim");
2575            assert!(pool_has(&assembly, &shared_key).await);
2576
2577            drop(assembly);
2578            drop_schema(&dsn, &schema).await;
2579            drop_schema(&dsn, &format!("{}_engine", schema)).await;
2580            drop_schema(&dsn, &format!("{}_blobs", schema)).await;
2581            let _ = std::fs::remove_dir_all(&root);
2582        }
2583    }
2584}