diff --git a/pkg/dotc1z/engine/pebble/adapter_reader.go b/pkg/dotc1z/engine/pebble/adapter_reader.go index 6d7d43523..d6a4260ac 100644 --- a/pkg/dotc1z/engine/pebble/adapter_reader.go +++ b/pkg/dotc1z/engine/pebble/adapter_reader.go @@ -447,7 +447,7 @@ func (e *Engine) ListSyncs(ctx context.Context, req *reader_v2.SyncsReaderServic if err != nil { return nil, err } - iter, err := e.NewIter(&pebble.IterOptions{ + iter, err := e.expansion.NewIter(&pebble.IterOptions{ LowerBound: lower, UpperBound: upper, }) diff --git a/pkg/dotc1z/engine/pebble/bulk_import_dedupe_test.go b/pkg/dotc1z/engine/pebble/bulk_import_dedupe_test.go index e6664ca0c..12ad1303e 100644 --- a/pkg/dotc1z/engine/pebble/bulk_import_dedupe_test.go +++ b/pkg/dotc1z/engine/pebble/bulk_import_dedupe_test.go @@ -36,7 +36,7 @@ func TestBulkImportAbortAfterEngineClose(t *testing.T) { store, b := startBulkImport(t, ctx) require.NoError(t, b.AddResourceTypes(ctx, v2.ResourceType_builder{Id: "app"}.Build())) - require.NoError(t, store.PebbleEngine().Close()) + require.NoError(t, store.Expansion().PebbleEngine().Close()) require.NotPanics(t, b.Abort, "Abort after engine Close must tear down staging dirs, not panic") // And the other retained-handle shape: Finish after Abort/Close is diff --git a/pkg/dotc1z/engine/pebble/cleanup_test.go b/pkg/dotc1z/engine/pebble/cleanup_test.go index 53263cdf4..2688cbdd1 100644 --- a/pkg/dotc1z/engine/pebble/cleanup_test.go +++ b/pkg/dotc1z/engine/pebble/cleanup_test.go @@ -66,14 +66,14 @@ func TestResetForNewSyncReclaimsDiskImmediately(t *testing.T) { dataLo := []byte{versionV3, typeResourceType} dataHi := []byte{versionV3, typeEngineMeta} - before, err := e.EstimateDiskUsage(dataLo, dataHi) + before, err := e.Expansion().EstimateDiskUsage(dataLo, dataHi) require.NoErrorf(t, err, "EstimateDiskUsage (before)") require.NotZero(t, before, "sanity: expected non-zero on-disk usage for the finished sync's data span") // Replacement sync: StartNewSync excises the prior sync's data. _, err = a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoErrorf(t, err, "StartNewSync (replacement)") - after, err := e.EstimateDiskUsage(dataLo, dataHi) + after, err := e.Expansion().EstimateDiskUsage(dataLo, dataHi) require.NoErrorf(t, err, "EstimateDiskUsage (after)") // The excise drops fully-covered SSTs from the manifest, so the // data span's estimated usage collapses to (near) zero immediately diff --git a/pkg/dotc1z/engine/pebble/endsync_concurrent_writers_test.go b/pkg/dotc1z/engine/pebble/endsync_concurrent_writers_test.go index ba568e783..14ae22e4a 100644 --- a/pkg/dotc1z/engine/pebble/endsync_concurrent_writers_test.go +++ b/pkg/dotc1z/engine/pebble/endsync_concurrent_writers_test.go @@ -40,7 +40,7 @@ func TestEndSyncConcurrentWritersNeverMissByPrincipal(t *testing.T) { a := newAdapter(t) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() const workers = 8 type accepted struct { diff --git a/pkg/dotc1z/engine/pebble/engine.go b/pkg/dotc1z/engine/pebble/engine.go index 41bdca197..b799c1166 100644 --- a/pkg/dotc1z/engine/pebble/engine.go +++ b/pkg/dotc1z/engine/pebble/engine.go @@ -142,9 +142,10 @@ type Engine struct { entIDLookup map[string][]entitlementIdentity entIDLookupBuiltGen uint64 - ledger Ledger - syncRuns SyncRuns - digests Digests + ledger Ledger + syncRuns SyncRuns + digests Digests + expansion Expansion // migratedOnOpen reports that this Open ran the in-place id-index // migration. The store layer uses it to mark a writable store dirty so @@ -206,6 +207,7 @@ func Open(ctx context.Context, dir string, opts ...Option) (*Engine, error) { e.ledger.e = e e.syncRuns.e = e e.digests.e = e + e.expansion.e = e if s, ok := pebbleOpts.Experimental.CompactionScheduler.(*pausableCompactionScheduler); ok { e.compactionScheduler = s } diff --git a/pkg/dotc1z/engine/pebble/expand_write_path_stats.go b/pkg/dotc1z/engine/pebble/expand_write_path_stats.go index 926630518..f187d21d5 100644 --- a/pkg/dotc1z/engine/pebble/expand_write_path_stats.go +++ b/pkg/dotc1z/engine/pebble/expand_write_path_stats.go @@ -1,5 +1,11 @@ package pebble +type Expansion struct { + e *Engine +} + +func (e *Engine) Expansion() *Expansion { return &e.expansion } + type ExpandWritePathStats struct { ExpandedCalls int64 ExpandedRows int64 @@ -7,11 +13,11 @@ type ExpandWritePathStats struct { SynthesizedRows int64 } -func (e *Engine) ExpandWritePathStats() ExpandWritePathStats { +func (x *Expansion) ExpandWritePathStats() ExpandWritePathStats { return ExpandWritePathStats{ - ExpandedCalls: e.expandedWriteCalls.Load(), - ExpandedRows: e.expandedWriteRows.Load(), - SynthesizedCalls: e.synthesizedWriteCalls.Load(), - SynthesizedRows: e.synthesizedWriteRows.Load(), + ExpandedCalls: x.e.expandedWriteCalls.Load(), + ExpandedRows: x.e.expandedWriteRows.Load(), + SynthesizedCalls: x.e.synthesizedWriteCalls.Load(), + SynthesizedRows: x.e.synthesizedWriteRows.Load(), } } diff --git a/pkg/dotc1z/engine/pebble/fast_path_proof_verification_test.go b/pkg/dotc1z/engine/pebble/fast_path_proof_verification_test.go index 4af089d8a..b20e0fc58 100644 --- a/pkg/dotc1z/engine/pebble/fast_path_proof_verification_test.go +++ b/pkg/dotc1z/engine/pebble/fast_path_proof_verification_test.go @@ -94,7 +94,7 @@ func TestVerificationBindCurrentSyncDisarmsAllFastPathProofs(t *testing.T) { a := newAdapter(t) syncID, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() for _, kind := range []sourcecache.RowKind{ sourcecache.RowKindResources, sourcecache.RowKindEntitlements, @@ -118,7 +118,7 @@ func TestVerificationBulkImportFinishDisarmsAllFastPathProofs(t *testing.T) { a := newAdapter(t) syncID, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() bulk, err := e.StartBulkSyncImport(ctx, syncID, t.TempDir(), 1) require.NoError(t, err) defer bulk.Abort() @@ -154,20 +154,20 @@ func TestVerificationReplayClearCommittedPrefixDisarmsFastPath(t *testing.T) { prev := newAdapter(t) _, err := prev.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - putFastPathProofRow(t, prev.PebbleEngine(), kind, "scope-a", "source", 0) - sealReplaySource(ctx, t, prev.PebbleEngine(), kind, "scope-a") + putFastPathProofRow(t, prev.Expansion().PebbleEngine(), kind, "scope-a", "source", 0) + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), kind, "scope-a") current := newAdapter(t) _, err = current.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) for row := range 3 { - putFastPathProofRow(t, current.PebbleEngine(), kind, "scope-a", "destination", row) + putFastPathProofRow(t, current.Expansion().PebbleEngine(), kind, "scope-a", "destination", row) } - armFastPathProof(current.PebbleEngine(), kind) - require.True(t, fastPathProofArmed(current.PebbleEngine(), kind), "fault premise was not armed") - current.PebbleEngine().test.sourceCacheReplayBatchRows = 2 + armFastPathProof(current.Expansion().PebbleEngine(), kind) + require.True(t, fastPathProofArmed(current.Expansion().PebbleEngine(), kind), "fault premise was not armed") + current.Expansion().PebbleEngine().test.sourceCacheReplayBatchRows = 2 commitCalls := 0 - current.PebbleEngine().test.sourceCacheReplayClearCommitHook = func( + current.Expansion().PebbleEngine().test.sourceCacheReplayClearCommitHook = func( gotKind string, _ int, _ bool, @@ -183,31 +183,31 @@ func TestVerificationReplayClearCommittedPrefixDisarmsFastPath(t *testing.T) { var replayErr error switch kind { case sourcecache.RowKindResources: - _, replayErr = current.PebbleEngine().ReplaySourceCacheResources( + _, replayErr = current.Expansion().PebbleEngine().ReplaySourceCacheResources( ctx, - prev.PebbleEngine(), + prev.Expansion().PebbleEngine(), "scope-a", ) case sourcecache.RowKindEntitlements: - _, replayErr = current.PebbleEngine().ReplaySourceCacheEntitlements( + _, replayErr = current.Expansion().PebbleEngine().ReplaySourceCacheEntitlements( ctx, - prev.PebbleEngine(), + prev.Expansion().PebbleEngine(), "scope-a", ) case sourcecache.RowKindGrants: - _, replayErr = current.PebbleEngine().ReplaySourceCacheGrants( + _, replayErr = current.Expansion().PebbleEngine().ReplaySourceCacheGrants( ctx, - prev.PebbleEngine(), + prev.Expansion().PebbleEngine(), "scope-a", ) } require.ErrorIs(t, replayErr, injected) require.Equal(t, 2, commitCalls, "fault did not occur after one committed clear batch") - armedAfterFailure := fastPathProofArmed(current.PebbleEngine(), kind) - current.PebbleEngine().test.sourceCacheReplayClearCommitHook = nil + armedAfterFailure := fastPathProofArmed(current.Expansion().PebbleEngine(), kind) + current.Expansion().PebbleEngine().test.sourceCacheReplayClearCommitHook = nil - putFastPathProofRow(t, current.PebbleEngine(), kind, "scope-b", "destination", 2) - require.NoError(t, auditSourceScopeBiconditional(current.PebbleEngine()), + putFastPathProofRow(t, current.Expansion().PebbleEngine(), kind, "scope-b", "destination", 2) + require.NoError(t, auditSourceScopeBiconditional(current.Expansion().PebbleEngine()), "colliding write used stale empty-keyspace proof after committed clear") require.False(t, armedAfterFailure, "committed clear did not consume %s fast-path proof", kind) }) @@ -219,15 +219,15 @@ func TestVerificationReplayWriteBarrierDrainsBeforeClose(t *testing.T) { prev := newAdapter(t) _, err := prev.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - putFastPathProofRow(t, prev.PebbleEngine(), sourcecache.RowKindResources, "scope-a", "source", 0) - sealReplaySource(ctx, t, prev.PebbleEngine(), sourcecache.RowKindResources, "scope-a") + putFastPathProofRow(t, prev.Expansion().PebbleEngine(), sourcecache.RowKindResources, "scope-a", "source", 0) + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), sourcecache.RowKindResources, "scope-a") current := newAdapter(t) _, err = current.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) enteredCommit := make(chan struct{}) releaseCommit := make(chan struct{}) - current.PebbleEngine().test.sourceCacheReplayCommitHook = func(kind string, rows int, final bool) error { + current.Expansion().PebbleEngine().test.sourceCacheReplayCommitHook = func(kind string, rows int, final bool) error { if kind != string(sourcecache.RowKindResources) || rows != 1 || !final { return fmt.Errorf("unexpected replay commit kind=%q rows=%d final=%t", kind, rows, final) } @@ -238,9 +238,9 @@ func TestVerificationReplayWriteBarrierDrainsBeforeClose(t *testing.T) { replayDone := make(chan error, 1) go func() { - _, replayErr := current.PebbleEngine().ReplaySourceCacheResources( + _, replayErr := current.Expansion().PebbleEngine().ReplaySourceCacheResources( context.Background(), - prev.PebbleEngine(), + prev.Expansion().PebbleEngine(), "scope-a", ) replayDone <- replayErr @@ -253,7 +253,7 @@ func TestVerificationReplayWriteBarrierDrainsBeforeClose(t *testing.T) { closeDone := make(chan error, 1) go func() { - closeDone <- current.PebbleEngine().Close() + closeDone <- current.Expansion().PebbleEngine().Close() }() select { case closeErr := <-closeDone: diff --git a/pkg/dotc1z/engine/pebble/ingest_facts_security_test.go b/pkg/dotc1z/engine/pebble/ingest_facts_security_test.go index 36ecdff31..0fb647de6 100644 --- a/pkg/dotc1z/engine/pebble/ingest_facts_security_test.go +++ b/pkg/dotc1z/engine/pebble/ingest_facts_security_test.go @@ -49,7 +49,7 @@ func TestSecurity_IngestScansRejectNonCanonicalKeys(t *testing.T) { a := newAdapter(t) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() require.NoError(t, e.db.UnsafeForTesting().Set(tc.key, nil, nil)) err = scans[scan](ctx, e) @@ -68,7 +68,7 @@ func TestIngestScansVisitEachGroupOnce(t *testing.T) { a := newAdapter(t) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() resource := v2.Resource_builder{Id: v2.ResourceId_builder{ResourceType: "app", Resource: "github"}.Build()}.Build() grant := func(entID, principalID string) *v2.Grant { diff --git a/pkg/dotc1z/engine/pebble/ingest_repair_orphan_index_test.go b/pkg/dotc1z/engine/pebble/ingest_repair_orphan_index_test.go index f648e18dc..11c12f793 100644 --- a/pkg/dotc1z/engine/pebble/ingest_repair_orphan_index_test.go +++ b/pkg/dotc1z/engine/pebble/ingest_repair_orphan_index_test.go @@ -48,7 +48,7 @@ func TestDanglingPrincipalSweepHealsAllOrphanIndexEntries(t *testing.T) { a := newAdapter(t) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() // "ghost" has index entries but no grant rows and no resource row: // the all-orphan shape. Without the heal this would visit as @@ -87,7 +87,7 @@ func TestEnsureGrantIndexesClearsDeferredMarker(t *testing.T) { a := newAdapter(t) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() // The deferred regime arms the marker. require.NoError(t, a.Grants().StoreExpandedGrants(ctx, mkV2Grant("", "ent-x", "user", "alice"))) @@ -119,7 +119,7 @@ func TestHealOrphanPrincipalIndexEntriesKeepsLiveRows(t *testing.T) { a := newAdapter(t) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() require.NoError(t, a.PutGrants(ctx, mkV2Grant("", "ent-live", "user", "victim"))) plantOrphanPrincipalIndexEntry(t, e, "victim", "ghost-ent") @@ -152,7 +152,7 @@ func TestIngestScansFailLoudOnMalformedKeys(t *testing.T) { a := newAdapter(t) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() // A grant-family key whose tail has too few tuple components. bad := append(encodeGrantPrefix(), 0x00, 'x') require.NoError(t, e.db.UnsafeForTesting().Set(bad, nil, nil)) @@ -169,7 +169,7 @@ func TestIngestScansFailLoudOnMalformedKeys(t *testing.T) { a := newAdapter(t) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() bad := append(encodeEntitlementPrefix(), 0x00, 'x') require.NoError(t, e.db.UnsafeForTesting().Set(bad, nil, nil)) @@ -182,7 +182,7 @@ func TestIngestScansFailLoudOnMalformedKeys(t *testing.T) { a := newAdapter(t) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() bad := []byte{versionV3, typeIndex, idxGrantByPrincipal, 0x00, 'x'} require.NoError(t, e.db.UnsafeForTesting().Set(bad, nil, nil)) @@ -201,7 +201,7 @@ func TestIngestScanSurfaceAfterCloseReturnsClosing(t *testing.T) { a := newAdapter(t) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() require.NoError(t, e.Close()) visit2 := func(string, string) error { return nil } @@ -227,7 +227,7 @@ func TestDanglingPrincipalSweepKeepsMixedPopulations(t *testing.T) { a := newAdapter(t) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() // A real grant to a principal with no resource row (a genuine // dangling reference) PLUS a planted orphan entry under the same diff --git a/pkg/dotc1z/engine/pebble/merge_accessor.go b/pkg/dotc1z/engine/pebble/merge_accessor.go index 0118dfb71..00ef50cdf 100644 --- a/pkg/dotc1z/engine/pebble/merge_accessor.go +++ b/pkg/dotc1z/engine/pebble/merge_accessor.go @@ -31,11 +31,11 @@ type engineAccessor interface { // PebbleEngine returns the engine. Nil-safe so AsEngine can probe // arbitrary writers. -func (e *Engine) PebbleEngine() *Engine { - if e == nil { +func (x *Expansion) PebbleEngine() *Engine { + if x == nil { return nil } - return e + return x.e } // AsEngine recovers the underlying *Engine from a connectorstore.Writer @@ -57,11 +57,11 @@ func AsEngine(w connectorstore.Writer) (*Engine, bool) { // counters, labeled with the caller's phase name. Diagnostic only: used to // attribute wall time lost to background compactions triggered by large // ingests (e.g. the synthesized-grant layer segments) during benchmark runs. -func (e *Engine) LogCompactionMetrics(ctx context.Context, phase string) { - if e == nil || e.db == nil { +func (x *Expansion) LogCompactionMetrics(ctx context.Context, phase string) { + if x == nil || x.e.db == nil { return } - m := e.db.Metrics() + m := x.e.db.Metrics() ctxzap.Extract(ctx).Info("pebble compaction metrics", zap.String("phase", phase), zap.Int64("compact_count", m.Compact.Count), diff --git a/pkg/dotc1z/engine/pebble/merge_surface.go b/pkg/dotc1z/engine/pebble/merge_surface.go index 2a220775c..36b2284f4 100644 --- a/pkg/dotc1z/engine/pebble/merge_surface.go +++ b/pkg/dotc1z/engine/pebble/merge_surface.go @@ -30,60 +30,60 @@ import ( // Get reads a key. Same contract as pebble.DB.Get, including // pebble.ErrNotFound and the caller-owned closer. -func (e *Engine) Get(key []byte) ([]byte, io.Closer, error) { - if e.db == nil { +func (x *Expansion) Get(key []byte) ([]byte, io.Closer, error) { + if x.e.db == nil { return nil, nil, ErrEngineClosing } - return e.db.Get(key) + return x.e.db.Get(key) } // NewIter opens an iterator. Same contract as pebble.DB.NewIter. -func (e *Engine) NewIter(o *pebble.IterOptions) (*pebble.Iterator, error) { - if e.db == nil { +func (x *Expansion) NewIter(o *pebble.IterOptions) (*pebble.Iterator, error) { + if x.e.db == nil { return nil, ErrEngineClosing } - return e.db.NewIter(o) + return x.e.db.NewIter(o) } // Metrics returns pebble's metrics snapshot, or nil after Close. -func (e *Engine) Metrics() *pebble.Metrics { - if e.db == nil { +func (x *Expansion) Metrics() *pebble.Metrics { + if x.e.db == nil { return nil } - return e.db.Metrics() + return x.e.db.Metrics() } // EstimateDiskUsage estimates on-disk size of the key range. -func (e *Engine) EstimateDiskUsage(start, end []byte) (uint64, error) { - if e.db == nil { +func (x *Expansion) EstimateDiskUsage(start, end []byte) (uint64, error) { + if x.e.db == nil { return 0, ErrEngineClosing } - return e.db.EstimateDiskUsage(start, end) + return x.e.db.EstimateDiskUsage(start, end) } // DropKeyRange stages and commits a range tombstone over [start, end). -func (e *Engine) DropKeyRange(start, end []byte, o *pebble.WriteOptions) error { - if e.db == nil { +func (x *Expansion) DropKeyRange(start, end []byte, o *pebble.WriteOptions) error { + if x.e.db == nil { return ErrEngineClosing } - return e.db.DropKeyRange(start, end, o) + return x.e.db.DropKeyRange(start, end, o) } // IngestSSTs ingests externally built SSTs (created on the engine FS). -func (e *Engine) IngestSSTs(ctx context.Context, paths []string) error { - if e.db == nil { +func (x *Expansion) IngestSSTs(ctx context.Context, paths []string) error { + if x.e.db == nil { return ErrEngineClosing } - return e.db.IngestSSTs(ctx, paths) + return x.e.db.IngestSSTs(ctx, paths) } // ReplaceRangeWithSSTs atomically replaces span with the given SSTs // (pebble IngestAndExcise). -func (e *Engine) ReplaceRangeWithSSTs(ctx context.Context, paths []string, span pebble.KeyRange) error { - if e.db == nil { +func (x *Expansion) ReplaceRangeWithSSTs(ctx context.Context, paths []string, span pebble.KeyRange) error { + if x.e.db == nil { return ErrEngineClosing } - return e.db.ReplaceRangeWithSSTs(ctx, paths, span) + return x.e.db.ReplaceRangeWithSSTs(ctx, paths, span) } // NewFoldBatch mints the compactor's generic staged batch (the one @@ -95,11 +95,11 @@ func (e *Engine) ReplaceRangeWithSSTs(ctx context.Context, paths []string, span // and a closed engine here means the compactor's call-ordering fence // (merge completes before save/Close) was violated, which is a // programming error, not a runtime condition to handle. -func (e *Engine) NewFoldBatch() *FoldBatch { - if e.db == nil { +func (x *Expansion) NewFoldBatch() *FoldBatch { + if x.e.db == nil { panic("pebble engine: NewFoldBatch on a closed engine — the fold must complete before the engine closes") } - return e.db.NewFoldBatch() + return x.e.db.NewFoldBatch() } // UnsafeForTesting returns the raw *pebble.DB — the single raw escape @@ -107,9 +107,9 @@ func (e *Engine) NewFoldBatch() *FoldBatch { // the production API cannot express. Panics outside `go test` // (testing.Testing(), enforced inside rawdb) and, explicitly, after // Close. -func (e *Engine) UnsafeForTesting() *pebble.DB { - if e.db == nil { +func (x *Expansion) UnsafeForTesting() *pebble.DB { + if x.e.db == nil { panic("pebble engine: UnsafeForTesting on a closed engine") } - return e.db.UnsafeForTesting() + return x.e.db.UnsafeForTesting() } diff --git a/pkg/dotc1z/engine/pebble/obligations_on_failure_test.go b/pkg/dotc1z/engine/pebble/obligations_on_failure_test.go index 2ebe24069..3663b732b 100644 --- a/pkg/dotc1z/engine/pebble/obligations_on_failure_test.go +++ b/pkg/dotc1z/engine/pebble/obligations_on_failure_test.go @@ -194,7 +194,7 @@ func TestFailedMutationPathsFireObligations(t *testing.T) { // covers stochastically. name: "deferred-arm-commit-failure", run: func(t *testing.T, ctx context.Context, a *Adapter, _ string) error { - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() e.test.recordCommitHook = func() error { return injected } t.Cleanup(func() { e.test.recordCommitHook = nil }) return a.Grants().StoreExpandedGrants(ctx, mkV2Grant("", "ent-x", "user", "alice")) @@ -209,7 +209,7 @@ func TestFailedMutationPathsFireObligations(t *testing.T) { require.ErrorIs(t, err, pebble.ErrNotFound, "no row of the failed batch may have committed") }, converge: func(t *testing.T, ctx context.Context, a *Adapter, syncID string) { - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() e.test.recordCommitHook = nil // Retry the same write, then seal: the armed marker's // rebuild must leave by_principal complete. @@ -230,7 +230,7 @@ func TestFailedMutationPathsFireObligations(t *testing.T) { // discoverable as unfinished, so a retried EndSync converges. name: "endsync-stamp-commit-failure", run: func(t *testing.T, ctx context.Context, a *Adapter, _ string) error { - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() require.NoError(t, a.PutGrants(ctx, mkV2Grant("", "ent-y", "user", "bob"))) e.test.endSyncStampHook = func() error { return injected } t.Cleanup(func() { e.test.endSyncStampHook = nil }) @@ -248,9 +248,9 @@ func TestFailedMutationPathsFireObligations(t *testing.T) { require.Equal(t, syncID, unfinished.GetSyncId()) }, converge: func(t *testing.T, ctx context.Context, a *Adapter, syncID string) { - a.PebbleEngine().test.endSyncStampHook = nil + a.Expansion().PebbleEngine().test.endSyncStampHook = nil require.NoError(t, a.EndSync(ctx), "a retried EndSync must converge") - rec, err := a.PebbleEngine().SyncRuns().GetSyncRunRecord(ctx, syncID) + rec, err := a.Expansion().PebbleEngine().SyncRuns().GetSyncRunRecord(ctx, syncID) require.NoError(t, err) require.NotNil(t, rec.GetEndedAt()) }, @@ -267,7 +267,7 @@ func TestFailedMutationPathsFireObligations(t *testing.T) { err = tc.run(t, ctx, a, syncID) require.Error(t, err, "the case must fail mid-mutation; a clean run proves nothing about error-path obligations") require.ErrorIs(t, err, injected) - tc.obligations(t, ctx, a.PebbleEngine(), syncID) + tc.obligations(t, ctx, a.Expansion().PebbleEngine(), syncID) if tc.converge != nil { tc.converge(t, ctx, a, syncID) } diff --git a/pkg/dotc1z/engine/pebble/pagination_advance_security_test.go b/pkg/dotc1z/engine/pebble/pagination_advance_security_test.go index 2d4904914..9d403e5da 100644 --- a/pkg/dotc1z/engine/pebble/pagination_advance_security_test.go +++ b/pkg/dotc1z/engine/pebble/pagination_advance_security_test.go @@ -21,7 +21,7 @@ func TestSecurity_PageTokensAdvancePastHostileRows(t *testing.T) { a := newAdapter(t) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() parent := v3.ResourceRef_builder{ResourceTypeId: "group", ResourceId: "g"}.Build() rb := e.db.NewRecordBatch() diff --git a/pkg/dotc1z/engine/pebble/resource_leak_ride_along_test.go b/pkg/dotc1z/engine/pebble/resource_leak_ride_along_test.go index 4fe4ec359..0eebd1291 100644 --- a/pkg/dotc1z/engine/pebble/resource_leak_ride_along_test.go +++ b/pkg/dotc1z/engine/pebble/resource_leak_ride_along_test.go @@ -37,7 +37,7 @@ func TestResourceLeakRideAlongAdequacy(t *testing.T) { rb := e.db.NewRecordBatch() require.NoError(t, rb.Commit(pebble.NoSync), "empty commit") require.NoError(t, rb.Close(), "close after commit") - it, err := e.NewIter(&pebble.IterOptions{}) + it, err := e.Expansion().NewIter(&pebble.IterOptions{}) require.NoError(t, err) require.NoError(t, it.Close()) require.NoError(t, e.Close()) @@ -98,7 +98,7 @@ func TestResourceLeakRideAlongAdequacy(t *testing.T) { t.Run("planted-leaked-iterator-fails-close", func(t *testing.T) { e := openLeakProbeEngine(t) - it, err := e.NewIter(&pebble.IterOptions{}) + it, err := e.Expansion().NewIter(&pebble.IterOptions{}) require.NoError(t, err) _ = it // deliberately leaked require.ErrorContains(t, e.Close(), "leaked", diff --git a/pkg/dotc1z/engine/pebble/rollback_expansion.go b/pkg/dotc1z/engine/pebble/rollback_expansion.go index 78be00301..649ce2622 100644 --- a/pkg/dotc1z/engine/pebble/rollback_expansion.go +++ b/pkg/dotc1z/engine/pebble/rollback_expansion.go @@ -43,8 +43,8 @@ type RollbackExpansionResult struct { // Sealing, or any pending pass on a finished sync, is dropped so a replay // starts a fresh pass. The caller seals: grant indexes, digests, and stats are // stale until the next EndSync. -func (e *Engine) RollbackExpansion(ctx context.Context, syncID string, dryRun, preserveSuspect bool) (*RollbackExpansionResult, error) { - record, err := e.syncRuns.GetSyncRunRecord(ctx, syncID) +func (x *Expansion) RollbackExpansion(ctx context.Context, syncID string, dryRun, preserveSuspect bool) (*RollbackExpansionResult, error) { + record, err := x.e.syncRuns.GetSyncRunRecord(ctx, syncID) if err != nil { return nil, fmt.Errorf("pebble: rollback could not load sync %q: %w", syncID, err) } @@ -52,7 +52,7 @@ func (e *Engine) RollbackExpansion(ctx context.Context, syncID string, dryRun, p if !finished && record.GetSyncToken() != "" { return nil, fmt.Errorf("%w: sync %q holds a legacy checkpoint", ErrRollbackIncompleteCollection, syncID) } - _, phase, err := e.ledger.PendingWork(ctx, 0, 1) + _, phase, err := x.e.ledger.PendingWork(ctx, 0, 1) if err != nil { return nil, fmt.Errorf("pebble: rollback could not read pending work: %w", err) } @@ -71,17 +71,17 @@ func (e *Engine) RollbackExpansion(ctx context.Context, syncID string, dryRun, p res := &RollbackExpansionResult{} var scanned int if dryRun { - scanned, err = e.rollbackExpansionRounds(ctx, false, preserveSuspect, res) + scanned, err = x.rollbackExpansionRounds(ctx, false, preserveSuspect, res) } else { - if err := e.SetCurrentSync(ctx, syncID); err != nil { + if err := x.e.SetCurrentSync(ctx, syncID); err != nil { return nil, err } if phase != c1zstore.LedgerQueueAbsent { - if err := e.ledger.Drop(ctx); err != nil { + if err := x.e.ledger.Drop(ctx); err != nil { return nil, fmt.Errorf("pebble: rollback could not drop pending pass: %w", err) } } - scanned, err = e.rollbackExpansionRounds(ctx, true, preserveSuspect, res) + scanned, err = x.rollbackExpansionRounds(ctx, true, preserveSuspect, res) } if err != nil { return nil, fmt.Errorf("pebble: rollback failed: %w", err) @@ -94,7 +94,7 @@ func (e *Engine) RollbackExpansion(ctx context.Context, syncID string, dryRun, p // rollbackExpansionRounds commits each round's deletes and clears when write // is set, and returns the number of grants scanned. -func (e *Engine) rollbackExpansionRounds(ctx context.Context, write, preserveSuspect bool, res *RollbackExpansionResult) (int, error) { +func (x *Expansion) rollbackExpansionRounds(ctx context.Context, write, preserveSuspect bool, res *RollbackExpansionResult) (int, error) { lower := encodeGrantPrefix() upper := upperBoundOf(lower) scanned := 0 @@ -106,23 +106,23 @@ func (e *Engine) rollbackExpansionRounds(ctx context.Context, write, preserveSus var n int var err error if !write { - next, n, err = e.rollbackExpansionRound(lower, upper, nil, preserveSuspect, res) + next, n, err = x.rollbackExpansionRound(lower, upper, nil, preserveSuspect, res) } else { - err = e.withWrite(func() error { - if err := e.requireCurrentSync(); err != nil { + err = x.e.withWrite(func() error { + if err := x.e.requireCurrentSync(); err != nil { return err } - batch := e.db.NewRecordBatch() + batch := x.e.db.NewRecordBatch() defer batch.Close() var roundErr error - next, n, roundErr = e.rollbackExpansionRound(lower, upper, batch, preserveSuspect, res) + next, n, roundErr = x.rollbackExpansionRound(lower, upper, batch, preserveSuspect, res) if roundErr != nil { return roundErr } if batch.Empty() { return nil } - return batch.Commit(writeOpts(e.opts.durability)) + return batch.Commit(writeOpts(x.e.opts.durability)) }) } if err != nil { @@ -138,13 +138,13 @@ func (e *Engine) rollbackExpansionRounds(ctx context.Context, write, preserveSus // in [lower, upper), staging each delete or clear into batch when batch is // non-nil. The iterator is closed on return, before the caller commits. // Returns the next round's lower bound, or nil when the range is exhausted. -func (e *Engine) rollbackExpansionRound( +func (x *Expansion) rollbackExpansionRound( lower, upper []byte, batch *rawdb.RecordBatch, preserveSuspect bool, res *RollbackExpansionResult, ) ([]byte, int, error) { - iter, err := e.db.NewIter(&pebble.IterOptions{LowerBound: lower, UpperBound: upper}) + iter, err := x.e.db.NewIter(&pebble.IterOptions{LowerBound: lower, UpperBound: upper}) if err != nil { return nil, 0, err } diff --git a/pkg/dotc1z/engine/pebble/rollback_expansion_bench_test.go b/pkg/dotc1z/engine/pebble/rollback_expansion_bench_test.go index 0959f49e9..5158a8667 100644 --- a/pkg/dotc1z/engine/pebble/rollback_expansion_bench_test.go +++ b/pkg/dotc1z/engine/pebble/rollback_expansion_bench_test.go @@ -46,7 +46,7 @@ func BenchmarkRollbackExpansionPebble(b *testing.B) { b.StartTimer() start := time.Now() - res, err := e.RollbackExpansion(ctx, syncID, false, false) + res, err := e.Expansion().RollbackExpansion(ctx, syncID, false, false) elapsed += time.Since(start) b.StopTimer() diff --git a/pkg/dotc1z/engine/pebble/rollback_expansion_test.go b/pkg/dotc1z/engine/pebble/rollback_expansion_test.go index 039f8e80b..9b594a680 100644 --- a/pkg/dotc1z/engine/pebble/rollback_expansion_test.go +++ b/pkg/dotc1z/engine/pebble/rollback_expansion_test.go @@ -131,12 +131,12 @@ func TestRollbackExpansionClassifiesBySources(t *testing.T) { syncID := finishedSyncWithGrants(t, e, rollbackClassificationGrants()...) before := grantKeyspace(t, e) - dry, err := e.RollbackExpansion(ctx, syncID, true, false) + dry, err := e.Expansion().RollbackExpansion(ctx, syncID, true, false) require.NoError(t, err) require.Equal(t, &RollbackExpansionResult{GrantsDeleted: 2, SourcesCleared: 2, SuspectConnectorSourced: 1}, dry) require.Equal(t, before, grantKeyspace(t, e), "a dry run writes nothing") - got, err := e.RollbackExpansion(ctx, syncID, false, false) + got, err := e.Expansion().RollbackExpansion(ctx, syncID, false, false) require.NoError(t, err) require.Equal(t, dry, got, "dry-run counts equal write counts") require.NoError(t, e.EndSync(ctx)) @@ -160,7 +160,7 @@ func TestRollbackExpansionPreservesSuspectGrants(t *testing.T) { e, _ := newTestEngine(t) syncID := finishedSyncWithGrants(t, e, rollbackClassificationGrants()...) - got, err := e.RollbackExpansion(ctx, syncID, false, true) + got, err := e.Expansion().RollbackExpansion(ctx, syncID, false, true) require.NoError(t, err) require.Equal(t, &RollbackExpansionResult{GrantsDeleted: 1, SourcesCleared: 2, SuspectConnectorSourced: 1, SuspectPreserved: 1}, got) require.NoError(t, e.EndSync(ctx)) @@ -192,7 +192,7 @@ func TestRollbackExpansionSuspectDeletePoisonsItsScope(t *testing.T) { require.Equal(t, "scope-suspect", records["app:github:ent-A:user:suspect"].GetSourceScopeKey()) require.Equal(t, "scope-cleared", records["app:github:ent-B:user:selfOnly"].GetSourceScopeKey()) - _, err := e.RollbackExpansion(ctx, syncID, false, preserve) + _, err := e.Expansion().RollbackExpansion(ctx, syncID, false, preserve) require.NoError(t, err) require.NoError(t, e.EndSync(ctx)) @@ -215,7 +215,7 @@ func TestRollbackExpansionNothingToRollBack(t *testing.T) { before := grantKeyspace(t, e) for _, dryRun := range []bool{true, false} { - _, err := e.RollbackExpansion(ctx, syncID, dryRun, false) + _, err := e.Expansion().RollbackExpansion(ctx, syncID, dryRun, false) require.ErrorIs(t, err, ErrNothingToRollBack) } require.Equal(t, before, grantKeyspace(t, e)) @@ -227,7 +227,7 @@ func TestRollbackExpansionNoGrants(t *testing.T) { syncID := finishedSyncWithGrants(t, e) for _, dryRun := range []bool{true, false} { - got, err := e.RollbackExpansion(ctx, syncID, dryRun, false) + got, err := e.Expansion().RollbackExpansion(ctx, syncID, dryRun, false) require.NoError(t, err) require.Equal(t, &RollbackExpansionResult{}, got) } @@ -236,7 +236,7 @@ func TestRollbackExpansionNoGrants(t *testing.T) { func TestRollbackExpansionUnknownSync(t *testing.T) { e, _ := newTestEngine(t) finishedSyncWithGrants(t, e, rollbackClassificationGrants()...) - _, err := e.RollbackExpansion(t.Context(), "2wM4dJ8i0yCZZWwGTS1yQ2jWbNC", true, false) + _, err := e.Expansion().RollbackExpansion(t.Context(), "2wM4dJ8i0yCZZWwGTS1yQ2jWbNC", true, false) require.ErrorIs(t, err, pebble.ErrNotFound) } @@ -270,7 +270,7 @@ func TestRollbackExpansionLedgerPhases(t *testing.T) { before := grantKeyspace(t, e) core, logs := observer.New(zap.WarnLevel) - got, err := e.RollbackExpansion(ctxzap.ToContext(ctx, zap.New(core)), syncID, dryRun, false) + got, err := e.Expansion().RollbackExpansion(ctxzap.ToContext(ctx, zap.New(core)), syncID, dryRun, false) _, phaseAfter, phaseErr := e.Ledger().PendingWork(ctx, 0, 1) require.NoError(t, phaseErr) if tc.refused { @@ -308,7 +308,7 @@ func TestRollbackExpansionRefusesUnfinishedLegacyCheckpoint(t *testing.T) { require.NoError(t, e.CheckpointSync(ctx, "legacy-token")) for _, dryRun := range []bool{true, false} { - _, err := e.RollbackExpansion(ctx, syncID, dryRun, false) + _, err := e.Expansion().RollbackExpansion(ctx, syncID, dryRun, false) require.ErrorIs(t, err, ErrRollbackIncompleteCollection) } } @@ -339,7 +339,7 @@ func TestRollbackExpansionMultiRound(t *testing.T) { grants, want := multiRoundRollbackGrants(n) syncID := finishedSyncWithGrants(t, e, grants...) - dry, err := e.RollbackExpansion(ctx, syncID, true, false) + dry, err := e.Expansion().RollbackExpansion(ctx, syncID, true, false) require.NoError(t, err) require.Equal(t, &want, dry) @@ -348,7 +348,7 @@ func TestRollbackExpansionMultiRound(t *testing.T) { commits++ return nil }) - got, err := e.RollbackExpansion(ctx, syncID, false, false) + got, err := e.Expansion().RollbackExpansion(ctx, syncID, false, false) e.db.SetRecordCommitTestHook(nil) require.NoError(t, err) require.Equal(t, &want, got) @@ -371,12 +371,12 @@ func TestRollbackExpansionCommitFailureLandsNothing(t *testing.T) { injected := errors.New("injected commit failure") e.db.SetRecordCommitTestHook(func() error { return injected }) - _, err := e.RollbackExpansion(ctx, syncID, false, false) + _, err := e.Expansion().RollbackExpansion(ctx, syncID, false, false) e.db.SetRecordCommitTestHook(nil) require.ErrorIs(t, err, injected) require.Equal(t, before, grantKeyspace(t, e)) - got, err := e.RollbackExpansion(ctx, syncID, false, false) + got, err := e.Expansion().RollbackExpansion(ctx, syncID, false, false) require.NoError(t, err, "a retry after a failed commit succeeds") require.Equal(t, 2, got.GrantsDeleted) require.NoError(t, e.EndSync(ctx)) @@ -389,7 +389,7 @@ func TestRollbackExpansionRetryAfterLaterRoundFailureConverges(t *testing.T) { clean, _ := newTestEngine(t) cleanSyncID := finishedSyncWithGrants(t, clean, grants...) - _, err := clean.RollbackExpansion(ctx, cleanSyncID, false, false) + _, err := clean.Expansion().RollbackExpansion(ctx, cleanSyncID, false, false) require.NoError(t, err) require.NoError(t, clean.EndSync(ctx)) @@ -404,11 +404,11 @@ func TestRollbackExpansionRetryAfterLaterRoundFailureConverges(t *testing.T) { } return nil }) - _, err = e.RollbackExpansion(ctx, syncID, false, false) + _, err = e.Expansion().RollbackExpansion(ctx, syncID, false, false) e.db.SetRecordCommitTestHook(nil) require.ErrorIs(t, err, injected) - got, err := e.RollbackExpansion(ctx, syncID, false, false) + got, err := e.Expansion().RollbackExpansion(ctx, syncID, false, false) require.NoError(t, err) require.Less(t, got.GrantsDeleted+got.SourcesCleared, want.GrantsDeleted+want.SourcesCleared, "the retry counts only rows the committed first round left") @@ -427,7 +427,7 @@ func TestRollbackExpansionMalformedRowAbortsRound(t *testing.T) { before := grantKeyspace(t, e) for _, dryRun := range []bool{true, false} { - _, err := e.RollbackExpansion(ctx, syncID, dryRun, false) + _, err := e.Expansion().RollbackExpansion(ctx, syncID, dryRun, false) require.ErrorContains(t, err, fmt.Sprintf("grant %x", plainKey)) } require.Equal(t, before, grantKeyspace(t, e)) @@ -440,7 +440,7 @@ func TestRollbackExpansionCanceledWritesNothing(t *testing.T) { ctx, cancel := context.WithCancel(t.Context()) cancel() - _, err := e.RollbackExpansion(ctx, syncID, false, false) + _, err := e.Expansion().RollbackExpansion(ctx, syncID, false, false) require.ErrorIs(t, err, context.Canceled) require.Equal(t, before, grantKeyspace(t, e)) } diff --git a/pkg/dotc1z/engine/pebble/source_cache_delete_bounds_verification_test.go b/pkg/dotc1z/engine/pebble/source_cache_delete_bounds_verification_test.go index 992304d42..b23ccc5f8 100644 --- a/pkg/dotc1z/engine/pebble/source_cache_delete_bounds_verification_test.go +++ b/pkg/dotc1z/engine/pebble/source_cache_delete_bounds_verification_test.go @@ -41,7 +41,7 @@ func TestVerificationScopedDeleteBatchBoundAndInterruptedRetry(t *testing.T) { } require.NoError(t, a.PutGrants(sourcecache.WithScope(t.Context(), "scope-a"), grants...)) return func(ctx context.Context) (int64, error) { - return a.PebbleEngine().DeleteGrantsByPrincipalsInScope( + return a.Expansion().PebbleEngine().DeleteGrantsByPrincipalsInScope( ctx, "scope-a", map[string]struct{}{"alice": {}}, @@ -64,7 +64,7 @@ func TestVerificationScopedDeleteBatchBoundAndInterruptedRetry(t *testing.T) { } require.NoError(t, a.PutGrants(sourcecache.WithScope(t.Context(), "scope-a"), grants...)) return func(ctx context.Context) (int64, error) { - return a.PebbleEngine().DeleteGrantsByExternalIDsInScope(ctx, "scope-a", ids) + return a.Expansion().PebbleEngine().DeleteGrantsByExternalIDsInScope(ctx, "scope-a", ids) } }, }, @@ -82,9 +82,9 @@ func TestVerificationScopedDeleteBatchBoundAndInterruptedRetry(t *testing.T) { SourceScopeKey: "scope-a", }.Build()) } - require.NoError(t, a.PebbleEngine().PutResourceRecords(t.Context(), records...)) + require.NoError(t, a.Expansion().PebbleEngine().PutResourceRecords(t.Context(), records...)) return func(ctx context.Context) (int64, error) { - return a.PebbleEngine().DeleteResourcesByIDsInScope( + return a.Expansion().PebbleEngine().DeleteResourcesByIDsInScope( ctx, "scope-a", map[string]struct{}{"shared": {}}, @@ -101,13 +101,13 @@ func TestVerificationScopedDeleteBatchBoundAndInterruptedRetry(t *testing.T) { _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) remove := tc.prepare(t, a) - require.Equal(t, rows, countKeys(t, a.PebbleEngine(), tc.primaryPrefix)) - require.Equal(t, rows, countKeys(t, a.PebbleEngine(), tc.indexPrefix)) + require.Equal(t, rows, countKeys(t, a.Expansion().PebbleEngine(), tc.primaryPrefix)) + require.Equal(t, rows, countKeys(t, a.Expansion().PebbleEngine(), tc.indexPrefix)) - a.PebbleEngine().test.sourceCacheDeleteBatchRows = 2 + a.Expansion().PebbleEngine().test.sourceCacheDeleteBatchRows = 2 injected := errors.New("verification scoped delete commit failure") commitCalls := 0 - a.PebbleEngine().test.sourceCacheDeleteCommitHook = func(kind string, batchRows int, _ bool) error { + a.Expansion().PebbleEngine().test.sourceCacheDeleteCommitHook = func(kind string, batchRows int, _ bool) error { require.Equal(t, tc.commitKind, kind) require.LessOrEqual(t, batchRows, 2) commitCalls++ @@ -120,17 +120,17 @@ func TestVerificationScopedDeleteBatchBoundAndInterruptedRetry(t *testing.T) { deleted, err := remove(ctx) require.ErrorIs(t, err, injected) require.Equal(t, int64(2), deleted, "only the first committed chunk may be reported") - require.Equal(t, rows-2, countKeys(t, a.PebbleEngine(), tc.primaryPrefix)) - require.Equal(t, rows-2, countKeys(t, a.PebbleEngine(), tc.indexPrefix)) - require.NoError(t, auditSourceScopeBiconditional(a.PebbleEngine())) + require.Equal(t, rows-2, countKeys(t, a.Expansion().PebbleEngine(), tc.primaryPrefix)) + require.Equal(t, rows-2, countKeys(t, a.Expansion().PebbleEngine(), tc.indexPrefix)) + require.NoError(t, auditSourceScopeBiconditional(a.Expansion().PebbleEngine())) - a.PebbleEngine().test.sourceCacheDeleteCommitHook = nil + a.Expansion().PebbleEngine().test.sourceCacheDeleteCommitHook = nil deleted, err = remove(ctx) require.NoError(t, err) require.Equal(t, int64(rows-2), deleted) - require.Zero(t, countKeys(t, a.PebbleEngine(), tc.primaryPrefix)) - require.Zero(t, countKeys(t, a.PebbleEngine(), tc.indexPrefix)) - require.NoError(t, auditSourceScopeBiconditional(a.PebbleEngine())) + require.Zero(t, countKeys(t, a.Expansion().PebbleEngine(), tc.primaryPrefix)) + require.Zero(t, countKeys(t, a.Expansion().PebbleEngine(), tc.indexPrefix)) + require.NoError(t, auditSourceScopeBiconditional(a.Expansion().PebbleEngine())) }) } } @@ -148,7 +148,7 @@ func TestVerificationEntitlementDeleteBumpsLookupGenPerChunk(t *testing.T) { a := newAdapter(t) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() const rows = 5 records := make([]*v3.EntitlementRecord, 0, rows) @@ -196,12 +196,12 @@ func TestVerificationScopedDeleteBatchFinalCloseOwnership(t *testing.T) { require.NoError(t, err) grant := scGrant("member", "alice", false) require.NoError(t, a.PutGrants(sourcecache.WithScope(ctx, "scope-a"), grant)) - rec, err := a.PebbleEngine().GetGrantRecord(ctx, grant.GetId()) + rec, err := a.Expansion().PebbleEngine().GetGrantRecord(ctx, grant.GetId()) require.NoError(t, err) id, err := grantIdentityFromRecord(rec) require.NoError(t, err) - deletes := newSourceCacheDeleteBatch(a.PebbleEngine(), "ownership", "scope-a", cockroachpebble.NoSync) + deletes := newSourceCacheDeleteBatch(a.Expansion().PebbleEngine(), "ownership", "scope-a", cockroachpebble.NoSync) require.NoError(t, deletes.batch.StageSourceScopeOrphanIndexDelete( encodeGrantBySourceScopeIndexKey("scope-a", id), )) diff --git a/pkg/dotc1z/engine/pebble/source_cache_engine_test.go b/pkg/dotc1z/engine/pebble/source_cache_engine_test.go index e65a926e7..fc3840a3c 100644 --- a/pkg/dotc1z/engine/pebble/source_cache_engine_test.go +++ b/pkg/dotc1z/engine/pebble/source_cache_engine_test.go @@ -27,10 +27,10 @@ func TestInvalidateSourceCacheReplayStateCommitFailureIsAtomic(t *testing.T) { _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) require.NoError(t, a.PutGrants(sourcecache.WithScope(ctx, scopeA), scGrant("member", "alice", false))) - require.NoError(t, a.PebbleEngine().PutSourceCacheEntry(ctx, string(sourcecache.RowKindGrants), scopeA, "validator")) + require.NoError(t, a.Expansion().PebbleEngine().PutSourceCacheEntry(ctx, string(sourcecache.RowKindGrants), scopeA, "validator")) require.NoError(t, a.EndSync(ctx)) - eng := a.PebbleEngine() + eng := a.Expansion().PebbleEngine() require.Equal(t, 1, countKeys(t, eng, SourceCacheEntryLowerBound())) require.Equal(t, 1, countKeys(t, eng, GrantBySourceScopeLowerBound())) injected := errors.New("source-cache invalidation commit failed") @@ -76,7 +76,7 @@ func TestSourceCacheEntryCRUD(t *testing.T) { _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() _, err = e.GetSourceCacheEntry(ctx, "grants", scopeA) require.ErrorIs(t, err, pebble.ErrNotFound) @@ -117,41 +117,41 @@ func TestSourceCacheReplayGrantsAcrossEngines(t *testing.T) { require.NoError(t, prev.PutGrants(scopedCtx, gExpandable, gPlain)) require.NoError(t, prev.PutGrants(sourcecache.WithScope(ctx, scopeB), gOtherScope)) require.NoError(t, prev.PutGrants(ctx, gUnstamped)) - require.NoError(t, prev.PebbleEngine().PutSourceCacheEntry(ctx, "grants", scopeA, "etag-a")) - sealReplaySource(ctx, t, prev.PebbleEngine(), sourcecache.RowKindGrants, scopeA) + require.NoError(t, prev.Expansion().PebbleEngine().PutSourceCacheEntry(ctx, "grants", scopeA, "etag-a")) + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), sourcecache.RowKindGrants, scopeA) cur := newAdapter(t) _, err = cur.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - res, err := cur.PebbleEngine().ReplaySourceCacheGrants(ctx, prev.PebbleEngine(), scopeA) + res, err := cur.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, prev.Expansion().PebbleEngine(), scopeA) require.NoError(t, err) require.Equal(t, int64(2), res.Rows) require.True(t, res.NeedsExpansion, "replay must report the expandable grant so the syncer can arm expansion") // Replayed rows are readable by ID and carry their expansion flag. - got, err := cur.PebbleEngine().GetGrantRecord(ctx, gExpandable.GetId()) + got, err := cur.Expansion().PebbleEngine().GetGrantRecord(ctx, gExpandable.GetId()) require.NoError(t, err) require.True(t, got.GetNeedsExpansion()) require.Equal(t, scopeA, got.GetSourceScopeKey()) - _, err = cur.PebbleEngine().GetGrantRecord(ctx, gPlain.GetId()) + _, err = cur.Expansion().PebbleEngine().GetGrantRecord(ctx, gPlain.GetId()) require.NoError(t, err) // Rows outside the scope are not copied. - _, err = cur.PebbleEngine().GetGrantRecord(ctx, gOtherScope.GetId()) + _, err = cur.Expansion().PebbleEngine().GetGrantRecord(ctx, gOtherScope.GetId()) require.ErrorIs(t, err, pebble.ErrNotFound) - _, err = cur.PebbleEngine().GetGrantRecord(ctx, gUnstamped.GetId()) + _, err = cur.Expansion().PebbleEngine().GetGrantRecord(ctx, gUnstamped.GetId()) require.ErrorIs(t, err, pebble.ErrNotFound) // The by_source_scope index came across too: a second replay FROM the // current store (as next sync's previous) finds the same rows. The // manifest entry does NOT come across — writing it (with the unchanged // validator) is orchestration's job, mirrored here before sealing. - sealReplaySource(ctx, t, cur.PebbleEngine(), sourcecache.RowKindGrants, scopeA) + sealReplaySource(ctx, t, cur.Expansion().PebbleEngine(), sourcecache.RowKindGrants, scopeA) next := newAdapter(t) _, err = next.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - res2, err := next.PebbleEngine().ReplaySourceCacheGrants(ctx, cur.PebbleEngine(), scopeA) + res2, err := next.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, cur.Expansion().PebbleEngine(), scopeA) require.NoError(t, err) require.Equal(t, int64(2), res2.Rows) } @@ -170,24 +170,24 @@ func TestSourceCacheReplayIsIdempotent(t *testing.T) { g1 := scGrant("member", "alice", true) g2 := scGrant("member", "bob", false) require.NoError(t, prev.PutGrants(sourcecache.WithScope(ctx, scopeA), g1, g2)) - sealReplaySource(ctx, t, prev.PebbleEngine(), sourcecache.RowKindGrants, scopeA) + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), sourcecache.RowKindGrants, scopeA) cur := newAdapter(t) _, err = cur.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - res1, err := cur.PebbleEngine().ReplaySourceCacheGrants(ctx, prev.PebbleEngine(), scopeA) + res1, err := cur.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, prev.Expansion().PebbleEngine(), scopeA) require.NoError(t, err) require.Equal(t, int64(2), res1.Rows) - res2, err := cur.PebbleEngine().ReplaySourceCacheGrants(ctx, prev.PebbleEngine(), scopeA) + res2, err := cur.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, prev.Expansion().PebbleEngine(), scopeA) require.NoError(t, err) require.Equal(t, int64(2), res2.Rows, "second replay converges, not errors") require.Equal(t, res1.NeedsExpansion, res2.NeedsExpansion) // State after double replay is exactly the two rows, once each. var seen int - require.NoError(t, cur.PebbleEngine().IterateGrants(ctx, func(*v3.GrantRecord) bool { + require.NoError(t, cur.Expansion().PebbleEngine().IterateGrants(ctx, func(*v3.GrantRecord) bool { seen++ return true })) @@ -210,26 +210,26 @@ func TestSourceCacheReplayRejectsStaleIndexEntries(t *testing.T) { require.NoError(t, prev.PutGrants(sourcecache.WithScope(ctx, scopeB), g)) // Seal BEFORE the corruption below: scopeA's sealed count is an honest // zero, so the stale index is detected by the walk, not the count. - sealReplaySource(ctx, t, prev.PebbleEngine(), sourcecache.RowKindGrants, scopeA, scopeB) + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), sourcecache.RowKindGrants, scopeA, scopeB) // ...plus a stale index entry claiming it belongs to scopeA. - rec, err := prev.PebbleEngine().GetGrantRecord(ctx, g.GetId()) + rec, err := prev.Expansion().PebbleEngine().GetGrantRecord(ctx, g.GetId()) require.NoError(t, err) staleIdx := replayTestGrantScopeIndexKey(t, scopeA, rec) - require.NoError(t, prev.PebbleEngine().UnsafeForTesting().Set(staleIdx, nil, nil)) + require.NoError(t, prev.Expansion().PebbleEngine().Expansion().UnsafeForTesting().Set(staleIdx, nil, nil)) cur := newAdapter(t) _, err = cur.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - _, err = cur.PebbleEngine().ReplaySourceCacheGrants(ctx, prev.PebbleEngine(), scopeA) + _, err = cur.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, prev.Expansion().PebbleEngine(), scopeA) require.ErrorContains(t, err, "preflight", "a stale source index must fail closed before any destination mutation") - _, err = cur.PebbleEngine().GetGrantRecord(ctx, g.GetId()) + _, err = cur.Expansion().PebbleEngine().GetGrantRecord(ctx, g.GetId()) require.ErrorIs(t, err, pebble.ErrNotFound) // The genuine scope still replays. - res, err := cur.PebbleEngine().ReplaySourceCacheGrants(ctx, prev.PebbleEngine(), scopeB) + res, err := cur.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, prev.Expansion().PebbleEngine(), scopeB) require.NoError(t, err) require.Equal(t, int64(1), res.Rows) } @@ -259,7 +259,7 @@ func TestEntitlementRestampCleansOldScopeIndex(t *testing.T) { require.NoError(t, prev.PutEntitlements(sourcecache.WithScope(ctx, scopeA), ent1)) ent1b := v2.Entitlement_builder{Id: entID, Resource: res1, DisplayName: "member (renamed)"}.Build() require.NoError(t, prev.PutEntitlements(sourcecache.WithScope(ctx, scopeB), ent1b)) - sealReplaySource(ctx, t, prev.PebbleEngine(), sourcecache.RowKindEntitlements, scopeA, scopeB) + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), sourcecache.RowKindEntitlements, scopeA, scopeB) cur := newAdapter(t) _, err = cur.StartNewSync(ctx, connectorstore.SyncTypeFull, "") @@ -269,14 +269,14 @@ func TestEntitlementRestampCleansOldScopeIndex(t *testing.T) { // (The index cleanup itself is proven by scopeB's clean preflight // below: a stale scopeA entry would fail it with "resolves to // primary stamped".) - _, err = cur.PebbleEngine().ReplaySourceCacheEntitlements(ctx, prev.PebbleEngine(), scopeA) + _, err = cur.Expansion().PebbleEngine().ReplaySourceCacheEntitlements(ctx, prev.Expansion().PebbleEngine(), scopeA) require.ErrorContains(t, err, "poisoned") // New scope: exactly the rewritten row. - resB, err := cur.PebbleEngine().ReplaySourceCacheEntitlements(ctx, prev.PebbleEngine(), scopeB) + resB, err := cur.Expansion().PebbleEngine().ReplaySourceCacheEntitlements(ctx, prev.Expansion().PebbleEngine(), scopeB) require.NoError(t, err) require.Equal(t, int64(1), resB.Rows) - got, err := cur.PebbleEngine().GetEntitlementRecord(ctx, entID) + got, err := cur.Expansion().PebbleEngine().GetEntitlementRecord(ctx, entID) require.NoError(t, err) require.Equal(t, scopeB, got.GetSourceScopeKey()) require.Equal(t, "member (renamed)", got.GetDisplayName()) @@ -299,7 +299,7 @@ func TestGrantAndResourceRestampCleanOldScopeIndexes(t *testing.T) { require.NoError(t, prev.PutResources(sourcecache.WithScope(ctx, scopeA), resource)) resource.SetDisplayName("Alice Updated") require.NoError(t, prev.PutResources(sourcecache.WithScope(ctx, scopeB), resource)) - sealReplaySourceMulti(ctx, t, prev.PebbleEngine(), map[sourcecache.RowKind][]string{ + sealReplaySourceMulti(ctx, t, prev.Expansion().PebbleEngine(), map[sourcecache.RowKind][]string{ sourcecache.RowKindGrants: {scopeA, scopeB}, sourcecache.RowKindResources: {scopeA, scopeB}, }) @@ -312,15 +312,15 @@ func TestGrantAndResourceRestampCleanOldScopeIndexes(t *testing.T) { // sources rather than replaying silently-empty sets. New scopes own // the rows exactly; their clean preflights prove the old index // entries were removed. - _, err = cur.PebbleEngine().ReplaySourceCacheGrants(ctx, prev.PebbleEngine(), scopeA) + _, err = cur.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, prev.Expansion().PebbleEngine(), scopeA) require.ErrorContains(t, err, "poisoned") - newGrant, err := cur.PebbleEngine().ReplaySourceCacheGrants(ctx, prev.PebbleEngine(), scopeB) + newGrant, err := cur.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, prev.Expansion().PebbleEngine(), scopeB) require.NoError(t, err) require.Equal(t, int64(1), newGrant.Rows) - _, err = cur.PebbleEngine().ReplaySourceCacheResources(ctx, prev.PebbleEngine(), scopeA) + _, err = cur.Expansion().PebbleEngine().ReplaySourceCacheResources(ctx, prev.Expansion().PebbleEngine(), scopeA) require.ErrorContains(t, err, "poisoned") - newResource, err := cur.PebbleEngine().ReplaySourceCacheResources(ctx, prev.PebbleEngine(), scopeB) + newResource, err := cur.Expansion().PebbleEngine().ReplaySourceCacheResources(ctx, prev.Expansion().PebbleEngine(), scopeB) require.NoError(t, err) require.Equal(t, int64(1), newResource.Rows) } @@ -353,7 +353,7 @@ func TestDeleteGrantsByPrincipalsInScope(t *testing.T) { gAliceOther := scGrant("owner", "alice", false) require.NoError(t, a.PutGrants(sourcecache.WithScope(ctx, scopeB), gAliceOther)) - deleted, err := a.PebbleEngine().DeleteGrantsByPrincipalsInScope(ctx, scopeA, map[string]struct{}{ + deleted, err := a.Expansion().PebbleEngine().DeleteGrantsByPrincipalsInScope(ctx, scopeA, map[string]struct{}{ "alice": {}, "bob": {}, "unknown": {}, // tombstone for a principal never synced — no-op @@ -361,20 +361,20 @@ func TestDeleteGrantsByPrincipalsInScope(t *testing.T) { require.NoError(t, err) require.Equal(t, int64(2), deleted) - _, err = a.PebbleEngine().GetGrantRecord(ctx, gAlice.GetId()) + _, err = a.Expansion().PebbleEngine().GetGrantRecord(ctx, gAlice.GetId()) require.ErrorIs(t, err, pebble.ErrNotFound) - _, err = a.PebbleEngine().GetGrantRecord(ctx, gBob.GetId()) + _, err = a.Expansion().PebbleEngine().GetGrantRecord(ctx, gBob.GetId()) require.ErrorIs(t, err, pebble.ErrNotFound) - _, err = a.PebbleEngine().GetGrantRecord(ctx, gAliceOther.GetId()) + _, err = a.Expansion().PebbleEngine().GetGrantRecord(ctx, gAliceOther.GetId()) require.NoError(t, err, "same principal in a different scope must survive") // Scope index entries went with the rows: a replay of scopeA from this // store copies nothing. - sealReplaySource(ctx, t, a.PebbleEngine(), sourcecache.RowKindGrants, scopeA) + sealReplaySource(ctx, t, a.Expansion().PebbleEngine(), sourcecache.RowKindGrants, scopeA) cur := newAdapter(t) _, err = cur.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - res, err := cur.PebbleEngine().ReplaySourceCacheGrants(ctx, a.PebbleEngine(), scopeA) + res, err := cur.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, a.Expansion().PebbleEngine(), scopeA) require.NoError(t, err) require.Zero(t, res.Rows) } @@ -393,17 +393,17 @@ func TestDeleteResourcesByIDsInScope(t *testing.T) { u3 := v2.Resource_builder{Id: v2.ResourceId_builder{ResourceType: "user", Resource: "u3"}.Build(), DisplayName: "U3"}.Build() require.NoError(t, a.PutResources(sourcecache.WithScope(ctx, scopeB), u3)) - deleted, err := a.PebbleEngine().DeleteResourcesByIDsInScope(ctx, scopeA, map[string]struct{}{ + deleted, err := a.Expansion().PebbleEngine().DeleteResourcesByIDsInScope(ctx, scopeA, map[string]struct{}{ "u1": {}, "u3": {}, // u3 is in scope B — must not die from an A-scoped tombstone }) require.NoError(t, err) require.Equal(t, int64(1), deleted) - _, err = a.PebbleEngine().GetResourceRecord(ctx, "user", "u1") + _, err = a.Expansion().PebbleEngine().GetResourceRecord(ctx, "user", "u1") require.ErrorIs(t, err, pebble.ErrNotFound) - _, err = a.PebbleEngine().GetResourceRecord(ctx, "user", "u2") + _, err = a.Expansion().PebbleEngine().GetResourceRecord(ctx, "user", "u2") require.NoError(t, err) - _, err = a.PebbleEngine().GetResourceRecord(ctx, "user", "u3") + _, err = a.Expansion().PebbleEngine().GetResourceRecord(ctx, "user", "u3") require.NoError(t, err, "tombstone must be scope-relative") } @@ -420,13 +420,13 @@ func TestDeleteGrantRecordBounded(t *testing.T) { require.NoError(t, a.PutGrants(ctx, g)) // Absent id (well-formed, no row): no-op, no error. - require.NoError(t, a.PebbleEngine().DeleteGrantRecordBounded(ctx, "group:g1:custom:member:user:ghost")) - _, err = a.PebbleEngine().GetGrantRecord(ctx, g.GetId()) + require.NoError(t, a.Expansion().PebbleEngine().DeleteGrantRecordBounded(ctx, "group:g1:custom:member:user:ghost")) + _, err = a.Expansion().PebbleEngine().GetGrantRecord(ctx, g.GetId()) require.NoError(t, err) // Present id: deleted. - require.NoError(t, a.PebbleEngine().DeleteGrantRecordBounded(ctx, g.GetId())) - _, err = a.PebbleEngine().GetGrantRecord(ctx, g.GetId()) + require.NoError(t, a.Expansion().PebbleEngine().DeleteGrantRecordBounded(ctx, g.GetId())) + _, err = a.Expansion().PebbleEngine().GetGrantRecord(ctx, g.GetId()) require.ErrorIs(t, err, pebble.ErrNotFound) } @@ -447,7 +447,7 @@ func TestSourceCacheReplayResourcesAndEntitlements(t *testing.T) { scoped := sourcecache.WithScope(ctx, scopeA) require.NoError(t, prev.PutResources(scoped, res1)) require.NoError(t, prev.PutEntitlements(scoped, ent1)) - sealReplaySourceMulti(ctx, t, prev.PebbleEngine(), map[sourcecache.RowKind][]string{ + sealReplaySourceMulti(ctx, t, prev.Expansion().PebbleEngine(), map[sourcecache.RowKind][]string{ sourcecache.RowKindResources: {scopeA}, sourcecache.RowKindEntitlements: {scopeA}, }) @@ -456,19 +456,19 @@ func TestSourceCacheReplayResourcesAndEntitlements(t *testing.T) { _, err = cur.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - rres, err := cur.PebbleEngine().ReplaySourceCacheResources(ctx, prev.PebbleEngine(), scopeA) + rres, err := cur.Expansion().PebbleEngine().ReplaySourceCacheResources(ctx, prev.Expansion().PebbleEngine(), scopeA) require.NoError(t, err) require.Equal(t, int64(1), rres.Rows) - eres, err := cur.PebbleEngine().ReplaySourceCacheEntitlements(ctx, prev.PebbleEngine(), scopeA) + eres, err := cur.Expansion().PebbleEngine().ReplaySourceCacheEntitlements(ctx, prev.Expansion().PebbleEngine(), scopeA) require.NoError(t, err) require.Equal(t, int64(1), eres.Rows) - gotRes, err := cur.PebbleEngine().GetResourceRecord(ctx, "group", "g1") + gotRes, err := cur.Expansion().PebbleEngine().GetResourceRecord(ctx, "group", "g1") require.NoError(t, err) require.Equal(t, "Group One", gotRes.GetDisplayName()) require.Equal(t, scopeA, gotRes.GetSourceScopeKey()) - gotEnt, err := cur.PebbleEngine().GetEntitlementRecord(ctx, entID) + gotEnt, err := cur.Expansion().PebbleEngine().GetEntitlementRecord(ctx, entID) require.NoError(t, err) require.Equal(t, scopeA, gotEnt.GetSourceScopeKey()) } @@ -494,7 +494,7 @@ func TestReplayInvalidatesEntitlementIDLookup(t *testing.T) { entID := "group:g1:member" ent1 := v2.Entitlement_builder{Id: entID, Resource: res1, DisplayName: "member"}.Build() require.NoError(t, prev.PutEntitlements(sourcecache.WithScope(ctx, scopeA), ent1)) - sealReplaySource(ctx, t, prev.PebbleEngine(), sourcecache.RowKindEntitlements, scopeA) + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), sourcecache.RowKindEntitlements, scopeA) cur := newAdapter(t) _, err = cur.StartNewSync(ctx, connectorstore.SyncTypeFull, "") @@ -502,23 +502,23 @@ func TestReplayInvalidatesEntitlementIDLookup(t *testing.T) { // Force the lazy bare-id map to build BEFORE the replay (empty // keyspace → entID resolves to nothing). - _, err = cur.PebbleEngine().GetEntitlementRecord(ctx, entID) + _, err = cur.Expansion().PebbleEngine().GetEntitlementRecord(ctx, entID) require.Error(t, err, "sanity: entitlement must not resolve before replay") - rres, err := cur.PebbleEngine().ReplaySourceCacheEntitlements(ctx, prev.PebbleEngine(), scopeA) + rres, err := cur.Expansion().PebbleEngine().ReplaySourceCacheEntitlements(ctx, prev.Expansion().PebbleEngine(), scopeA) require.NoError(t, err) require.Equal(t, int64(1), rres.Rows) // The replayed row must be visible through the bare-id lookup (the // map built above is stale and must have been invalidated) ... - gotEnt, err := cur.PebbleEngine().GetEntitlementRecord(ctx, entID) + gotEnt, err := cur.Expansion().PebbleEngine().GetEntitlementRecord(ctx, entID) require.NoError(t, err, "replayed entitlement must resolve by external id after a pre-replay map build") require.Equal(t, scopeA, gotEnt.GetSourceScopeKey()) // ... and, the actual production stake: a delta tombstone against the // replayed row must DELETE it, not silently no-op. - require.NoError(t, cur.PebbleEngine().DeleteEntitlementRecord(ctx, entID)) - _, err = cur.PebbleEngine().GetEntitlementRecord(ctx, entID) + require.NoError(t, cur.Expansion().PebbleEngine().DeleteEntitlementRecord(ctx, entID)) + _, err = cur.Expansion().PebbleEngine().GetEntitlementRecord(ctx, entID) require.Error(t, err, "tombstoned replayed entitlement must be gone") } @@ -546,20 +546,20 @@ func TestStoreExpandedGrantsPreservesSourceScope(t *testing.T) { }.Build()) require.NoError(t, a.Grants().StoreExpandedGrants(ctx, rewrite)) - got, err := a.PebbleEngine().GetGrantRecord(ctx, g.GetId()) + got, err := a.Expansion().PebbleEngine().GetGrantRecord(ctx, g.GetId()) require.NoError(t, err) require.Equal(t, scopeA, got.GetSourceScopeKey(), "expander rewrite clobbered the source scope stamp") require.True(t, got.GetNeedsExpansion(), "expansion side-state must be preserved too") // And the index survives: a replay from this store still finds the row. - sealReplaySource(ctx, t, a.PebbleEngine(), sourcecache.RowKindGrants, scopeA) + sealReplaySource(ctx, t, a.Expansion().PebbleEngine(), sourcecache.RowKindGrants, scopeA) cur := newAdapter(t) _, err = cur.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - res, err := cur.PebbleEngine().ReplaySourceCacheGrants(ctx, a.PebbleEngine(), scopeA) + res, err := cur.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, a.Expansion().PebbleEngine(), scopeA) require.NoError(t, err) require.Equal(t, int64(1), res.Rows) - replayed, err := cur.PebbleEngine().GetGrantRecord(ctx, g.GetId()) + replayed, err := cur.Expansion().PebbleEngine().GetGrantRecord(ctx, g.GetId()) require.NoError(t, err) // Replay-equivalence: expander-written Sources (self-source present) // are stripped so the current sync's expansion recomputes them from @@ -591,16 +591,16 @@ func TestSourceCacheReplayPreservesConnectorSetSources(t *testing.T) { }, }.Build()) require.NoError(t, prev.PutGrants(sourcecache.WithScope(ctx, scopeA), g)) - sealReplaySource(ctx, t, prev.PebbleEngine(), sourcecache.RowKindGrants, scopeA) + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), sourcecache.RowKindGrants, scopeA) cur := newAdapter(t) _, err = cur.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - res, err := cur.PebbleEngine().ReplaySourceCacheGrants(ctx, prev.PebbleEngine(), scopeA) + res, err := cur.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, prev.Expansion().PebbleEngine(), scopeA) require.NoError(t, err) require.Equal(t, int64(1), res.Rows) - replayed, err := cur.PebbleEngine().GetGrantRecord(ctx, g.GetId()) + replayed, err := cur.Expansion().PebbleEngine().GetGrantRecord(ctx, g.GetId()) require.NoError(t, err) require.Len(t, replayed.GetSources(), 1, "connector-set Sources must survive replay") _, ok := replayed.GetSources()["group:other:member"] diff --git a/pkg/dotc1z/engine/pebble/source_cache_replay_bounds_verification_test.go b/pkg/dotc1z/engine/pebble/source_cache_replay_bounds_verification_test.go index a084be602..ff95afc46 100644 --- a/pkg/dotc1z/engine/pebble/source_cache_replay_bounds_verification_test.go +++ b/pkg/dotc1z/engine/pebble/source_cache_replay_bounds_verification_test.go @@ -48,27 +48,27 @@ func TestVerificationReplayBatchBoundAndInterruptedRetry(t *testing.T) { SourceScopeKey: "scope-a", }.Build()) } - require.NoError(t, prev.PebbleEngine().PutResourceRecords(ctx, batch...)) + require.NoError(t, prev.Expansion().PebbleEngine().PutResourceRecords(ctx, batch...)) } - sealReplaySource(ctx, t, prev.PebbleEngine(), sourcecache.RowKindResources, "scope-a") + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), sourcecache.RowKindResources, "scope-a") bounded := newAdapter(t) _, err = bounded.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) var commitCalls, highWater int - bounded.PebbleEngine().test.sourceCacheReplayCommitHook = func(kind string, batchRows int, _ bool) error { + bounded.Expansion().PebbleEngine().test.sourceCacheReplayCommitHook = func(kind string, batchRows int, _ bool) error { require.Equal(t, "resources", kind) commitCalls++ highWater = max(highWater, batchRows) return nil } - res, err := bounded.PebbleEngine().ReplaySourceCacheResources(ctx, prev.PebbleEngine(), "scope-a") + res, err := bounded.Expansion().PebbleEngine().ReplaySourceCacheResources(ctx, prev.Expansion().PebbleEngine(), "scope-a") require.NoError(t, err) require.Equal(t, int64(rows), res.Rows) require.Equal(t, 2, commitCalls) require.NoError(t, validateBatchHighWater(highWater, replayBatchRows)) require.Equal(t, replayBatchRows, highWater) - require.Equal(t, rows, countKeys(t, bounded.PebbleEngine(), encodeResourcePrefix())) + require.Equal(t, rows, countKeys(t, bounded.Expansion().PebbleEngine(), encodeResourcePrefix())) interruptedEngine, interruptedDir := newTestEngine(t) interrupted := NewAdapter(interruptedEngine) @@ -76,77 +76,77 @@ func TestVerificationReplayBatchBoundAndInterruptedRetry(t *testing.T) { require.NoError(t, err) injected := errors.New("verification replay cut") commitCalls = 0 - interrupted.PebbleEngine().test.sourceCacheReplayCommitHook = func(_ string, _ int, _ bool) error { + interrupted.Expansion().PebbleEngine().test.sourceCacheReplayCommitHook = func(_ string, _ int, _ bool) error { commitCalls++ if commitCalls == 2 { return injected } return nil } - _, err = interrupted.PebbleEngine().ReplaySourceCacheResources(ctx, prev.PebbleEngine(), "scope-a") + _, err = interrupted.Expansion().PebbleEngine().ReplaySourceCacheResources(ctx, prev.Expansion().PebbleEngine(), "scope-a") require.ErrorIs(t, err, injected) - require.Equal(t, replayBatchRows, countKeys(t, interrupted.PebbleEngine(), encodeResourcePrefix()), + require.Equal(t, replayBatchRows, countKeys(t, interrupted.Expansion().PebbleEngine(), encodeResourcePrefix()), "only the first complete replay chunk may be visible after the injected cut") - require.Equal(t, replayBatchRows, countKeys(t, interrupted.PebbleEngine(), ResourceBySourceScopeLowerBound())) - require.Equal(t, rows, countKeys(t, prev.PebbleEngine(), encodeResourcePrefix()), "failed replay mutated source primaries") - require.NoError(t, auditSourceScopeBiconditional(prev.PebbleEngine())) + require.Equal(t, replayBatchRows, countKeys(t, interrupted.Expansion().PebbleEngine(), ResourceBySourceScopeLowerBound())) + require.Equal(t, rows, countKeys(t, prev.Expansion().PebbleEngine(), encodeResourcePrefix()), "failed replay mutated source primaries") + require.NoError(t, auditSourceScopeBiconditional(prev.Expansion().PebbleEngine())) - require.NoError(t, interrupted.PebbleEngine().Close()) + require.NoError(t, interrupted.Expansion().PebbleEngine().Close()) reopened, err := Open(ctx, filepath.Join(interruptedDir, "db")) require.NoError(t, err) t.Cleanup(func() { _ = reopened.Close() }) interrupted = NewAdapter(reopened) require.NoError(t, interrupted.SetCurrentSync(ctx, interruptedSyncID)) - res, err = interrupted.PebbleEngine().ReplaySourceCacheResources(ctx, prev.PebbleEngine(), "scope-a") + res, err = interrupted.Expansion().PebbleEngine().ReplaySourceCacheResources(ctx, prev.Expansion().PebbleEngine(), "scope-a") require.NoError(t, err) require.Equal(t, int64(rows), res.Rows) - require.Equal(t, rows, countKeys(t, interrupted.PebbleEngine(), encodeResourcePrefix())) - require.Equal(t, rows, countKeys(t, interrupted.PebbleEngine(), ResourceBySourceScopeLowerBound())) - require.NoError(t, auditSourceScopeBiconditional(interrupted.PebbleEngine())) + require.Equal(t, rows, countKeys(t, interrupted.Expansion().PebbleEngine(), encodeResourcePrefix())) + require.Equal(t, rows, countKeys(t, interrupted.Expansion().PebbleEngine(), ResourceBySourceScopeLowerBound())) + require.NoError(t, auditSourceScopeBiconditional(interrupted.Expansion().PebbleEngine())) cancelled := newAdapter(t) _, err = cancelled.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) cancelCtx, cancel := context.WithCancel(ctx) - cancelled.PebbleEngine().test.sourceCacheReplayCommitHook = func(_ string, _ int, final bool) error { + cancelled.Expansion().PebbleEngine().test.sourceCacheReplayCommitHook = func(_ string, _ int, final bool) error { if !final { cancel() } return nil } - _, err = cancelled.PebbleEngine().ReplaySourceCacheResources(cancelCtx, prev.PebbleEngine(), "scope-a") + _, err = cancelled.Expansion().PebbleEngine().ReplaySourceCacheResources(cancelCtx, prev.Expansion().PebbleEngine(), "scope-a") require.ErrorIs(t, err, context.Canceled) - require.Equal(t, replayBatchRows, countKeys(t, cancelled.PebbleEngine(), encodeResourcePrefix())) - require.Equal(t, rows, countKeys(t, prev.PebbleEngine(), encodeResourcePrefix()), "cancelled replay mutated source primaries") - require.NoError(t, auditSourceScopeBiconditional(prev.PebbleEngine())) - cancelled.PebbleEngine().test.sourceCacheReplayCommitHook = nil - res, err = cancelled.PebbleEngine().ReplaySourceCacheResources(ctx, prev.PebbleEngine(), "scope-a") + require.Equal(t, replayBatchRows, countKeys(t, cancelled.Expansion().PebbleEngine(), encodeResourcePrefix())) + require.Equal(t, rows, countKeys(t, prev.Expansion().PebbleEngine(), encodeResourcePrefix()), "cancelled replay mutated source primaries") + require.NoError(t, auditSourceScopeBiconditional(prev.Expansion().PebbleEngine())) + cancelled.Expansion().PebbleEngine().test.sourceCacheReplayCommitHook = nil + res, err = cancelled.Expansion().PebbleEngine().ReplaySourceCacheResources(ctx, prev.Expansion().PebbleEngine(), "scope-a") require.NoError(t, err) require.Equal(t, int64(rows), res.Rows) - require.Equal(t, rows, countKeys(t, cancelled.PebbleEngine(), encodeResourcePrefix())) - require.NoError(t, auditSourceScopeBiconditional(cancelled.PebbleEngine())) + require.Equal(t, rows, countKeys(t, cancelled.Expansion().PebbleEngine(), encodeResourcePrefix())) + require.NoError(t, auditSourceScopeBiconditional(cancelled.Expansion().PebbleEngine())) readFailed := newAdapter(t) _, err = readFailed.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) readErr := errors.New("verification source iterator failure") - readFailed.PebbleEngine().test.sourceCacheReplayReadHook = func(kind string, row int) error { + readFailed.Expansion().PebbleEngine().test.sourceCacheReplayReadHook = func(kind string, row int) error { require.Equal(t, "resources", kind) if row == replayBatchRows { return readErr } return nil } - _, err = readFailed.PebbleEngine().ReplaySourceCacheResources(ctx, prev.PebbleEngine(), "scope-a") + _, err = readFailed.Expansion().PebbleEngine().ReplaySourceCacheResources(ctx, prev.Expansion().PebbleEngine(), "scope-a") require.ErrorIs(t, err, readErr) - require.Equal(t, replayBatchRows, countKeys(t, readFailed.PebbleEngine(), encodeResourcePrefix())) - require.Equal(t, rows, countKeys(t, prev.PebbleEngine(), encodeResourcePrefix()), "source iterator failure mutated source") - readFailed.PebbleEngine().test.sourceCacheReplayReadHook = nil - res, err = readFailed.PebbleEngine().ReplaySourceCacheResources(ctx, prev.PebbleEngine(), "scope-a") + require.Equal(t, replayBatchRows, countKeys(t, readFailed.Expansion().PebbleEngine(), encodeResourcePrefix())) + require.Equal(t, rows, countKeys(t, prev.Expansion().PebbleEngine(), encodeResourcePrefix()), "source iterator failure mutated source") + readFailed.Expansion().PebbleEngine().test.sourceCacheReplayReadHook = nil + res, err = readFailed.Expansion().PebbleEngine().ReplaySourceCacheResources(ctx, prev.Expansion().PebbleEngine(), "scope-a") require.NoError(t, err) require.Equal(t, int64(rows), res.Rows) - require.Equal(t, rows, countKeys(t, readFailed.PebbleEngine(), encodeResourcePrefix())) - require.NoError(t, auditSourceScopeBiconditional(readFailed.PebbleEngine())) + require.Equal(t, rows, countKeys(t, readFailed.Expansion().PebbleEngine(), encodeResourcePrefix())) + require.NoError(t, auditSourceScopeBiconditional(readFailed.Expansion().PebbleEngine())) } // C10/C23/C27: every row kind exercises a real committed-prefix cut, hard @@ -176,7 +176,7 @@ func TestVerificationReplayCommittedPrefixRetryAllKinds(t *testing.T) { SourceScopeKey: "scope-a", }.Build()) } - require.NoError(t, prev.PebbleEngine().PutResourceRecords(ctx, rows...)) + require.NoError(t, prev.Expansion().PebbleEngine().PutResourceRecords(ctx, rows...)) case sourcecache.RowKindEntitlements: var rows []*v3.EntitlementRecord for i := range 3 { @@ -189,7 +189,7 @@ func TestVerificationReplayCommittedPrefixRetryAllKinds(t *testing.T) { SourceScopeKey: "scope-a", }.Build()) } - require.NoError(t, prev.PebbleEngine().PutEntitlementRecords(ctx, rows...)) + require.NoError(t, prev.Expansion().PebbleEngine().PutEntitlementRecords(ctx, rows...)) case sourcecache.RowKindGrants: var rows []*v3.GrantRecord for i := range 3 { @@ -207,21 +207,21 @@ func TestVerificationReplayCommittedPrefixRetryAllKinds(t *testing.T) { SourceScopeKey: "scope-a", }.Build()) } - require.NoError(t, prev.PebbleEngine().PutGrantRecords(ctx, rows...)) + require.NoError(t, prev.Expansion().PebbleEngine().PutGrantRecords(ctx, rows...)) } // Seal before the byte-dump so the untouched-source // comparisons below still hold. - sealReplaySource(ctx, t, prev.PebbleEngine(), kind, "scope-a") - sourceBefore := dumpKeyRangeTest(t, prev.PebbleEngine(), nil, nil) + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), kind, "scope-a") + sourceBefore := dumpKeyRangeTest(t, prev.Expansion().PebbleEngine(), nil, nil) replay := func(replayCtx context.Context, dst *Engine) (SourceCacheReplayResult, error) { switch kind { case sourcecache.RowKindResources: - return dst.ReplaySourceCacheResources(replayCtx, prev.PebbleEngine(), "scope-a") + return dst.ReplaySourceCacheResources(replayCtx, prev.Expansion().PebbleEngine(), "scope-a") case sourcecache.RowKindEntitlements: - return dst.ReplaySourceCacheEntitlements(replayCtx, prev.PebbleEngine(), "scope-a") + return dst.ReplaySourceCacheEntitlements(replayCtx, prev.Expansion().PebbleEngine(), "scope-a") case sourcecache.RowKindGrants: - return dst.ReplaySourceCacheGrants(replayCtx, prev.PebbleEngine(), "scope-a") + return dst.ReplaySourceCacheGrants(replayCtx, prev.Expansion().PebbleEngine(), "scope-a") default: t.Fatalf("unsupported row kind %q", kind) return SourceCacheReplayResult{}, nil @@ -258,7 +258,7 @@ func TestVerificationReplayCommittedPrefixRetryAllKinds(t *testing.T) { require.Equal(t, 2, countKeys(t, interruptedEngine, family.primaryLo)) require.Equal(t, 2, countKeys(t, interruptedEngine, family.indexLo)) require.NoError(t, auditSourceScopeBiconditional(interruptedEngine)) - require.Equal(t, sourceBefore, dumpKeyRangeTest(t, prev.PebbleEngine(), nil, nil)) + require.Equal(t, sourceBefore, dumpKeyRangeTest(t, prev.Expansion().PebbleEngine(), nil, nil)) require.NoError(t, interruptedEngine.Close()) reopened, err := Open(ctx, filepath.Join(interruptedDir, "db")) @@ -273,31 +273,31 @@ func TestVerificationReplayCommittedPrefixRetryAllKinds(t *testing.T) { require.Equal(t, 3, countKeys(t, reopened, family.primaryLo)) require.Equal(t, 3, countKeys(t, reopened, family.indexLo)) require.NoError(t, auditSourceScopeBiconditional(reopened)) - require.Equal(t, sourceBefore, dumpKeyRangeTest(t, prev.PebbleEngine(), nil, nil)) + require.Equal(t, sourceBefore, dumpKeyRangeTest(t, prev.Expansion().PebbleEngine(), nil, nil)) for cut := range 3 { t.Run(fmt.Sprintf("read-error-cut-%d", cut), func(t *testing.T) { dst := newAdapter(t) _, err := dst.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - dst.PebbleEngine().test.sourceCacheReplayBatchRows = 2 + dst.Expansion().PebbleEngine().test.sourceCacheReplayBatchRows = 2 injected := errors.New("verification all-kind source read cut") - dst.PebbleEngine().test.sourceCacheReplayReadHook = func(_ string, row int) error { + dst.Expansion().PebbleEngine().test.sourceCacheReplayReadHook = func(_ string, row int) error { if row == cut { return injected } return nil } - _, err = replay(ctx, dst.PebbleEngine()) + _, err = replay(ctx, dst.Expansion().PebbleEngine()) require.ErrorIs(t, err, injected) - require.Equal(t, (cut/2)*2, countKeys(t, dst.PebbleEngine(), family.primaryLo)) - require.NoError(t, auditSourceScopeBiconditional(dst.PebbleEngine())) - require.Equal(t, sourceBefore, dumpKeyRangeTest(t, prev.PebbleEngine(), nil, nil)) - dst.PebbleEngine().test.sourceCacheReplayReadHook = nil - res, err := replay(ctx, dst.PebbleEngine()) + require.Equal(t, (cut/2)*2, countKeys(t, dst.Expansion().PebbleEngine(), family.primaryLo)) + require.NoError(t, auditSourceScopeBiconditional(dst.Expansion().PebbleEngine())) + require.Equal(t, sourceBefore, dumpKeyRangeTest(t, prev.Expansion().PebbleEngine(), nil, nil)) + dst.Expansion().PebbleEngine().test.sourceCacheReplayReadHook = nil + res, err := replay(ctx, dst.Expansion().PebbleEngine()) require.NoError(t, err) require.Equal(t, int64(3), res.Rows) - require.Equal(t, 3, countKeys(t, dst.PebbleEngine(), family.primaryLo)) + require.Equal(t, 3, countKeys(t, dst.Expansion().PebbleEngine(), family.primaryLo)) }) } @@ -305,24 +305,24 @@ func TestVerificationReplayCommittedPrefixRetryAllKinds(t *testing.T) { dst := newAdapter(t) _, err := dst.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - dst.PebbleEngine().test.sourceCacheReplayBatchRows = 2 + dst.Expansion().PebbleEngine().test.sourceCacheReplayBatchRows = 2 injected := errors.New("verification all-kind iterator terminal error") - dst.PebbleEngine().test.sourceCacheReplayIteratorErrorHook = func(gotKind string) error { + dst.Expansion().PebbleEngine().test.sourceCacheReplayIteratorErrorHook = func(gotKind string) error { require.Equal(t, string(kind), gotKind) return injected } - _, err = replay(ctx, dst.PebbleEngine()) + _, err = replay(ctx, dst.Expansion().PebbleEngine()) require.ErrorIs(t, err, injected) - require.Equal(t, 2, countKeys(t, dst.PebbleEngine(), family.primaryLo), + require.Equal(t, 2, countKeys(t, dst.Expansion().PebbleEngine(), family.primaryLo), "the final staged row must not commit after Iterator.Error") - require.NoError(t, auditSourceScopeBiconditional(dst.PebbleEngine())) - require.Equal(t, sourceBefore, dumpKeyRangeTest(t, prev.PebbleEngine(), nil, nil)) + require.NoError(t, auditSourceScopeBiconditional(dst.Expansion().PebbleEngine())) + require.Equal(t, sourceBefore, dumpKeyRangeTest(t, prev.Expansion().PebbleEngine(), nil, nil)) - dst.PebbleEngine().test.sourceCacheReplayIteratorErrorHook = nil - res, err := replay(ctx, dst.PebbleEngine()) + dst.Expansion().PebbleEngine().test.sourceCacheReplayIteratorErrorHook = nil + res, err := replay(ctx, dst.Expansion().PebbleEngine()) require.NoError(t, err) require.Equal(t, int64(3), res.Rows) - require.Equal(t, 3, countKeys(t, dst.PebbleEngine(), family.primaryLo)) + require.Equal(t, 3, countKeys(t, dst.Expansion().PebbleEngine(), family.primaryLo)) }) for cut := range 3 { @@ -330,24 +330,24 @@ func TestVerificationReplayCommittedPrefixRetryAllKinds(t *testing.T) { dst := newAdapter(t) _, err := dst.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - dst.PebbleEngine().test.sourceCacheReplayBatchRows = 2 + dst.Expansion().PebbleEngine().test.sourceCacheReplayBatchRows = 2 cancelCtx, cancel := context.WithCancel(ctx) - dst.PebbleEngine().test.sourceCacheReplayReadHook = func(_ string, row int) error { + dst.Expansion().PebbleEngine().test.sourceCacheReplayReadHook = func(_ string, row int) error { if row == cut { cancel() } return nil } - _, err = replay(cancelCtx, dst.PebbleEngine()) + _, err = replay(cancelCtx, dst.Expansion().PebbleEngine()) require.ErrorIs(t, err, context.Canceled) - require.Equal(t, ((cut+1)/2)*2, countKeys(t, dst.PebbleEngine(), family.primaryLo)) - require.NoError(t, auditSourceScopeBiconditional(dst.PebbleEngine())) - require.Equal(t, sourceBefore, dumpKeyRangeTest(t, prev.PebbleEngine(), nil, nil)) - dst.PebbleEngine().test.sourceCacheReplayReadHook = nil - res, err := replay(ctx, dst.PebbleEngine()) + require.Equal(t, ((cut+1)/2)*2, countKeys(t, dst.Expansion().PebbleEngine(), family.primaryLo)) + require.NoError(t, auditSourceScopeBiconditional(dst.Expansion().PebbleEngine())) + require.Equal(t, sourceBefore, dumpKeyRangeTest(t, prev.Expansion().PebbleEngine(), nil, nil)) + dst.Expansion().PebbleEngine().test.sourceCacheReplayReadHook = nil + res, err := replay(ctx, dst.Expansion().PebbleEngine()) require.NoError(t, err) require.Equal(t, int64(3), res.Rows) - require.Equal(t, 3, countKeys(t, dst.PebbleEngine(), family.primaryLo)) + require.Equal(t, 3, countKeys(t, dst.Expansion().PebbleEngine(), family.primaryLo)) }) } }) @@ -377,13 +377,13 @@ func TestVerificationEntitlementReplayBumpsLookupGenPerChunk(t *testing.T) { SourceScopeKey: "scope-a", }.Build()) } - require.NoError(t, prev.PebbleEngine().PutEntitlementRecords(ctx, records...)) - sealReplaySource(ctx, t, prev.PebbleEngine(), sourcecache.RowKindEntitlements, "scope-a") + require.NoError(t, prev.Expansion().PebbleEngine().PutEntitlementRecords(ctx, records...)) + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), sourcecache.RowKindEntitlements, "scope-a") dst := newAdapter(t) _, err = dst.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := dst.PebbleEngine() + e := dst.Expansion().PebbleEngine() e.test.sourceCacheReplayBatchRows = 2 genBefore := e.entIDLookupGen.Load() intermediates := 0 @@ -399,7 +399,7 @@ func TestVerificationEntitlementReplayBumpsLookupGenPerChunk(t *testing.T) { } return nil } - res, err := e.ReplaySourceCacheEntitlements(ctx, prev.PebbleEngine(), "scope-a") + res, err := e.ReplaySourceCacheEntitlements(ctx, prev.Expansion().PebbleEngine(), "scope-a") e.test.sourceCacheReplayCommitHook = nil require.NoError(t, err) require.Equal(t, int64(5), res.Rows) diff --git a/pkg/dotc1z/engine/pebble/source_scope_gate_verification_test.go b/pkg/dotc1z/engine/pebble/source_scope_gate_verification_test.go index 174b11156..c4c872862 100644 --- a/pkg/dotc1z/engine/pebble/source_scope_gate_verification_test.go +++ b/pkg/dotc1z/engine/pebble/source_scope_gate_verification_test.go @@ -70,7 +70,7 @@ func TestVerificationSourceScopeGateLifecycle(t *testing.T) { a := newAdapter(t) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() require.False(t, e.db.SourceScopeMayExist(), "fresh store must start unarmed") @@ -116,7 +116,7 @@ func TestVerificationSourceScopeGateArmsPerKind(t *testing.T) { a := newAdapter(t) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() // Two unstamped writes of the kind first: the second runs past // the fresh-first-batch skip, so the gate is what keeps it off @@ -186,7 +186,7 @@ func TestVerificationSourceScopeGateUnarmedSkipsPriorValueScan(t *testing.T) { a := newAdapter(t) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() // Burn the fresh-first-batch skip so the gate is the only thing // standing between the put and the read-before-write. @@ -279,7 +279,7 @@ func TestVerificationFoldBatchArmsSourceScopeGate(t *testing.T) { a := newAdapter(t) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() // Burn the fresh-first-batch grant skip so the later overwrite takes // its ordinary read-before-write path. @@ -305,7 +305,7 @@ func TestVerificationFoldBatchArmsSourceScopeGate(t *testing.T) { require.NoError(t, err) val, err := marshalRecord(stamped) require.NoError(t, err) - fb := e.NewFoldBatch() + fb := e.Expansion().NewFoldBatch() require.False(t, e.db.SourceScopeMayExist(), "minting the raw compaction batch must not arm the gate") require.NoError(t, fb.Set(encodeGrantIdentityKey(id), val)) require.False(t, e.db.SourceScopeMayExist(), "a stamped primary without an index must not arm the index-presence gate") @@ -339,7 +339,7 @@ func TestVerificationBulkImportFinishReprobesSourceScopeGate(t *testing.T) { a := newAdapter(t) syncID, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() require.False(t, e.db.SourceScopeMayExist(), "premise: fresh store unarmed") // The stamped row + entry a scoped import would install. Planted raw diff --git a/pkg/dotc1z/engine/pebble/source_scope_poison_verification_test.go b/pkg/dotc1z/engine/pebble/source_scope_poison_verification_test.go index c8d7757f8..028356f0f 100644 --- a/pkg/dotc1z/engine/pebble/source_scope_poison_verification_test.go +++ b/pkg/dotc1z/engine/pebble/source_scope_poison_verification_test.go @@ -128,24 +128,24 @@ func TestVerificationPoisonCrossScopeRestampOrders(t *testing.T) { for scope := range tc.clean { sealScopes = append(sealScopes, scope) } - sealReplaySource(ctx, t, prev.PebbleEngine(), sourcecache.RowKindGrants, sealScopes...) + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), sourcecache.RowKindGrants, sealScopes...) for _, scope := range tc.poisoned { dst := newAdapter(t) _, err := dst.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - before := dumpKeyRangeTest(t, dst.PebbleEngine(), nil, nil) - _, err = dst.PebbleEngine().ReplaySourceCacheGrants(ctx, prev.PebbleEngine(), scope) + before := dumpKeyRangeTest(t, dst.Expansion().PebbleEngine(), nil, nil) + _, err = dst.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, prev.Expansion().PebbleEngine(), scope) require.ErrorContains(t, err, "poisoned", "scope %q lost a row to a cross-scope write and must be refused as a replay source", scope) - require.Equal(t, before, dumpKeyRangeTest(t, dst.PebbleEngine(), nil, nil), + require.Equal(t, before, dumpKeyRangeTest(t, dst.Expansion().PebbleEngine(), nil, nil), "poison refusal must precede any destination mutation") } for scope, wantRows := range tc.clean { dst := newAdapter(t) _, err := dst.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - res, err := dst.PebbleEngine().ReplaySourceCacheGrants(ctx, prev.PebbleEngine(), scope) + res, err := dst.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, prev.Expansion().PebbleEngine(), scope) require.NoError(t, err, "undamaged scope %q must remain replayable", scope) require.Equal(t, wantRows, res.Rows) } @@ -172,14 +172,14 @@ func TestVerificationPoisonAllKindRestampAndUnscopedDelete(t *testing.T) { driver := newSourceScopeMutationDriver(t, prev, kind) require.NoError(t, driver.put(ctx, "scope-a")) require.NoError(t, driver.put(ctx, "scope-b")) - sealReplaySource(ctx, t, prev.PebbleEngine(), kind, "scope-a", "scope-b") + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), kind, "scope-a", "scope-b") dst := newAdapter(t) _, err = dst.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - _, err = driver.replay(ctx, dst.PebbleEngine(), prev.PebbleEngine(), "scope-a") + _, err = driver.replay(ctx, dst.Expansion().PebbleEngine(), prev.Expansion().PebbleEngine(), "scope-a") require.ErrorContains(t, err, "poisoned") - res, err := driver.replay(ctx, dst.PebbleEngine(), prev.PebbleEngine(), "scope-b") + res, err := driver.replay(ctx, dst.Expansion().PebbleEngine(), prev.Expansion().PebbleEngine(), "scope-b") require.NoError(t, err) require.Equal(t, int64(1), res.Rows) }) @@ -191,12 +191,12 @@ func TestVerificationPoisonAllKindRestampAndUnscopedDelete(t *testing.T) { driver := newSourceScopeMutationDriver(t, prev, kind) require.NoError(t, driver.put(ctx, "scope-a")) require.NoError(t, driver.delete(ctx)) - sealReplaySource(ctx, t, prev.PebbleEngine(), kind, "scope-a") + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), kind, "scope-a") dst := newAdapter(t) _, err = dst.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - _, err = driver.replay(ctx, dst.PebbleEngine(), prev.PebbleEngine(), "scope-a") + _, err = driver.replay(ctx, dst.Expansion().PebbleEngine(), prev.Expansion().PebbleEngine(), "scope-a") require.ErrorContains(t, err, "poisoned", "an unscoped delete removed scope-a's row; a silent empty replay would be data loss") }) @@ -225,13 +225,13 @@ func TestVerificationPoisonCrossScopeCanonicalTombstone(t *testing.T) { doomed := scGrant("member", "alice", false) kept := scGrant("member", "bob", false) require.NoError(t, prev.PutGrants(sourcecache.WithScope(ctx, "scope-a"), doomed, kept)) - require.NoError(t, prev.PebbleEngine().DeleteGrantRecordsBounded(ctx, []string{doomed.GetId()}, tc.actingScope)) - sealReplaySource(ctx, t, prev.PebbleEngine(), sourcecache.RowKindGrants, "scope-a") + require.NoError(t, prev.Expansion().PebbleEngine().DeleteGrantRecordsBounded(ctx, []string{doomed.GetId()}, tc.actingScope)) + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), sourcecache.RowKindGrants, "scope-a") dst := newAdapter(t) _, err = dst.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - res, err := dst.PebbleEngine().ReplaySourceCacheGrants(ctx, prev.PebbleEngine(), "scope-a") + res, err := dst.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, prev.Expansion().PebbleEngine(), "scope-a") if tc.poisoned { require.ErrorContains(t, err, "poisoned", "a canonical-ID tombstone acting for another scope removed scope-a's row") @@ -255,15 +255,15 @@ func TestVerificationPoisonScopedTombstonesDoNotSelfPoison(t *testing.T) { require.NoError(t, err) require.NoError(t, prev.PutGrants(sourcecache.WithScope(ctx, "scope-a"), scGrant("member", "alice", false), scGrant("member", "bob", false))) - deleted, err := prev.PebbleEngine().DeleteGrantsByPrincipalsInScope(ctx, "scope-a", map[string]struct{}{"alice": {}}) + deleted, err := prev.Expansion().PebbleEngine().DeleteGrantsByPrincipalsInScope(ctx, "scope-a", map[string]struct{}{"alice": {}}) require.NoError(t, err) require.Equal(t, int64(1), deleted) - sealReplaySource(ctx, t, prev.PebbleEngine(), sourcecache.RowKindGrants, "scope-a") + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), sourcecache.RowKindGrants, "scope-a") dst := newAdapter(t) _, err = dst.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - res, err := dst.PebbleEngine().ReplaySourceCacheGrants(ctx, prev.PebbleEngine(), "scope-a") + res, err := dst.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, prev.Expansion().PebbleEngine(), "scope-a") require.NoError(t, err, "a scope's own tombstones must not poison it") require.Equal(t, int64(1), res.Rows) }) @@ -275,15 +275,15 @@ func TestVerificationPoisonScopedTombstonesDoNotSelfPoison(t *testing.T) { u1 := v2.Resource_builder{Id: v2.ResourceId_builder{ResourceType: "user", Resource: "u1"}.Build()}.Build() u2 := v2.Resource_builder{Id: v2.ResourceId_builder{ResourceType: "user", Resource: "u2"}.Build()}.Build() require.NoError(t, prev.PutResources(sourcecache.WithScope(ctx, "scope-a"), u1, u2)) - deleted, err := prev.PebbleEngine().DeleteResourcesByIDsInScope(ctx, "scope-a", map[string]struct{}{"u1": {}}) + deleted, err := prev.Expansion().PebbleEngine().DeleteResourcesByIDsInScope(ctx, "scope-a", map[string]struct{}{"u1": {}}) require.NoError(t, err) require.Equal(t, int64(1), deleted) - sealReplaySource(ctx, t, prev.PebbleEngine(), sourcecache.RowKindResources, "scope-a") + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), sourcecache.RowKindResources, "scope-a") dst := newAdapter(t) _, err = dst.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - res, err := dst.PebbleEngine().ReplaySourceCacheResources(ctx, prev.PebbleEngine(), "scope-a") + res, err := dst.Expansion().PebbleEngine().ReplaySourceCacheResources(ctx, prev.Expansion().PebbleEngine(), "scope-a") require.NoError(t, err) require.Equal(t, int64(1), res.Rows) }) @@ -319,12 +319,12 @@ func TestVerificationPoisonReconciliationShapeRefusesReplay(t *testing.T) { // Reconciliation's shape: delete the placeholders by identity with no // acting scope, then write derived re-issued grants unscoped. - require.NoError(t, prev.PebbleEngine().DeleteGrantRecord(ctx, placeholderAlice.GetId())) - require.NoError(t, prev.PebbleEngine().DeleteGrantRecord(ctx, placeholderBob.GetId())) + require.NoError(t, prev.Expansion().PebbleEngine().DeleteGrantRecord(ctx, placeholderAlice.GetId())) + require.NoError(t, prev.Expansion().PebbleEngine().DeleteGrantRecord(ctx, placeholderBob.GetId())) require.NoError(t, prev.PutGrants(ctx, scGrant("member", "matched-alice", false), scGrant("member", "matched-bob", false))) - sealReplaySourceMulti(ctx, t, prev.PebbleEngine(), map[sourcecache.RowKind][]string{ + sealReplaySourceMulti(ctx, t, prev.Expansion().PebbleEngine(), map[sourcecache.RowKind][]string{ sourcecache.RowKindResources: {"scope-a"}, sourcecache.RowKindEntitlements: {"scope-a"}, sourcecache.RowKindGrants: {"scope-a"}, @@ -334,14 +334,14 @@ func TestVerificationPoisonReconciliationShapeRefusesReplay(t *testing.T) { _, err = dst.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - _, err = dst.PebbleEngine().ReplaySourceCacheGrants(ctx, prev.PebbleEngine(), "scope-a") + _, err = dst.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, prev.Expansion().PebbleEngine(), "scope-a") require.ErrorContains(t, err, "poisoned", "reconciliation deleted scope-a's placeholder grants; replay would silently drop them") - res, err := dst.PebbleEngine().ReplaySourceCacheResources(ctx, prev.PebbleEngine(), "scope-a") + res, err := dst.Expansion().PebbleEngine().ReplaySourceCacheResources(ctx, prev.Expansion().PebbleEngine(), "scope-a") require.NoError(t, err, "reconciliation never touches resource scopes") require.Equal(t, int64(1), res.Rows) - res, err = dst.PebbleEngine().ReplaySourceCacheEntitlements(ctx, prev.PebbleEngine(), "scope-a") + res, err = dst.Expansion().PebbleEngine().ReplaySourceCacheEntitlements(ctx, prev.Expansion().PebbleEngine(), "scope-a") require.NoError(t, err, "reconciliation never touches entitlement scopes") require.Equal(t, int64(1), res.Rows) } @@ -383,9 +383,9 @@ func TestVerificationPoisonSurvivesReopenAndInvalidationDropsIt(t *testing.T) { dst := newAdapter(t) _, err = dst.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - _, err = dst.PebbleEngine().ReplaySourceCacheGrants(ctx, re, "scope-a") + _, err = dst.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, re, "scope-a") require.ErrorContains(t, err, "poisoned") - res, err := dst.PebbleEngine().ReplaySourceCacheGrants(ctx, re, "scope-b") + res, err := dst.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, re, "scope-b") require.NoError(t, err) require.Equal(t, int64(1), res.Rows) diff --git a/pkg/dotc1z/engine/pebble/source_scope_verification_test.go b/pkg/dotc1z/engine/pebble/source_scope_verification_test.go index 72c80cb1c..d6c7ba1e5 100644 --- a/pkg/dotc1z/engine/pebble/source_scope_verification_test.go +++ b/pkg/dotc1z/engine/pebble/source_scope_verification_test.go @@ -33,7 +33,7 @@ type sourceScopeMutationDriver struct { func newSourceScopeMutationDriver(t *testing.T, a *Adapter, kind sourcecache.RowKind) sourceScopeMutationDriver { t.Helper() - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() switch kind { case sourcecache.RowKindResources: resource := v2pb.Resource_builder{ @@ -158,7 +158,7 @@ func assertSourceScopeReplayRows( dst := newAdapter(t) _, err := dst.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - res, err := driver.replay(ctx, dst.PebbleEngine(), source, scope) + res, err := driver.replay(ctx, dst.Expansion().PebbleEngine(), source, scope) require.NoError(t, err) require.Equal(t, want, res.Rows) } @@ -180,7 +180,7 @@ func assertSourceScopeReplayPoisoned( dst := newAdapter(t) _, err := dst.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - _, err = driver.replay(ctx, dst.PebbleEngine(), source, scope) + _, err = driver.replay(ctx, dst.Expansion().PebbleEngine(), source, scope) require.ErrorContains(t, err, "poisoned") } @@ -199,7 +199,7 @@ func TestVerificationSourceScopeMutationTransitions(t *testing.T) { a := newAdapter(t) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() driver := newSourceScopeMutationDriver(t, a, kind) // absent → A @@ -264,7 +264,7 @@ func TestVerificationMalformedAllKindDeleteFailsClosed(t *testing.T) { a := newAdapter(t) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() driver := newSourceScopeMutationDriver(t, a, kind) require.NoError(t, driver.put(ctx, "scope-a")) @@ -354,7 +354,7 @@ func TestVerificationReplayMatchesDirectTypedMaterialization(t *testing.T) { require.NoError(t, source.PutResources(scoped, resource)) require.NoError(t, source.PutEntitlements(scoped, entitlement)) require.NoError(t, source.PutGrants(scoped, grant)) - sealReplaySourceMulti(ctx, t, source.PebbleEngine(), map[sourcecache.RowKind][]string{ + sealReplaySourceMulti(ctx, t, source.Expansion().PebbleEngine(), map[sourcecache.RowKind][]string{ sourcecache.RowKindResources: {"scope-a"}, sourcecache.RowKindEntitlements: {"scope-a"}, sourcecache.RowKindGrants: {"scope-a"}, @@ -363,41 +363,41 @@ func TestVerificationReplayMatchesDirectTypedMaterialization(t *testing.T) { replayed := newAdapter(t) _, err = replayed.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - _, err = replayed.PebbleEngine().ReplaySourceCacheResources(ctx, source.PebbleEngine(), "scope-a") + _, err = replayed.Expansion().PebbleEngine().ReplaySourceCacheResources(ctx, source.Expansion().PebbleEngine(), "scope-a") require.NoError(t, err) - _, err = replayed.PebbleEngine().ReplaySourceCacheEntitlements(ctx, source.PebbleEngine(), "scope-a") + _, err = replayed.Expansion().PebbleEngine().ReplaySourceCacheEntitlements(ctx, source.Expansion().PebbleEngine(), "scope-a") require.NoError(t, err) - _, err = replayed.PebbleEngine().ReplaySourceCacheGrants(ctx, source.PebbleEngine(), "scope-a") + _, err = replayed.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, source.Expansion().PebbleEngine(), "scope-a") require.NoError(t, err) direct := newAdapter(t) _, err = direct.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - sourceResource, err := source.PebbleEngine().GetResourceRecord(ctx, "user", "alice") + sourceResource, err := source.Expansion().PebbleEngine().GetResourceRecord(ctx, "user", "alice") require.NoError(t, err) - sourceEntitlement, err := source.PebbleEngine().GetEntitlementRecord(ctx, entitlement.GetId()) + sourceEntitlement, err := source.Expansion().PebbleEngine().GetEntitlementRecord(ctx, entitlement.GetId()) require.NoError(t, err) - sourceGrant, err := source.PebbleEngine().GetGrantRecord(ctx, grant.GetId()) + sourceGrant, err := source.Expansion().PebbleEngine().GetGrantRecord(ctx, grant.GetId()) require.NoError(t, err) - require.NoError(t, direct.PebbleEngine().PutResourceRecords(ctx, sourceResource)) - require.NoError(t, direct.PebbleEngine().PutEntitlementRecords(ctx, sourceEntitlement)) - require.NoError(t, direct.PebbleEngine().PutGrantRecords(ctx, sourceGrant)) + require.NoError(t, direct.Expansion().PebbleEngine().PutResourceRecords(ctx, sourceResource)) + require.NoError(t, direct.Expansion().PebbleEngine().PutEntitlementRecords(ctx, sourceEntitlement)) + require.NoError(t, direct.Expansion().PebbleEngine().PutGrantRecords(ctx, sourceGrant)) - replayedResource, err := replayed.PebbleEngine().GetResourceRecord(ctx, "user", "alice") + replayedResource, err := replayed.Expansion().PebbleEngine().GetResourceRecord(ctx, "user", "alice") require.NoError(t, err) - directResource, err := direct.PebbleEngine().GetResourceRecord(ctx, "user", "alice") + directResource, err := direct.Expansion().PebbleEngine().GetResourceRecord(ctx, "user", "alice") require.NoError(t, err) require.True(t, proto.Equal(directResource, replayedResource)) - replayedEntitlement, err := replayed.PebbleEngine().GetEntitlementRecord(ctx, entitlement.GetId()) + replayedEntitlement, err := replayed.Expansion().PebbleEngine().GetEntitlementRecord(ctx, entitlement.GetId()) require.NoError(t, err) - directEntitlement, err := direct.PebbleEngine().GetEntitlementRecord(ctx, entitlement.GetId()) + directEntitlement, err := direct.Expansion().PebbleEngine().GetEntitlementRecord(ctx, entitlement.GetId()) require.NoError(t, err) require.True(t, proto.Equal(directEntitlement, replayedEntitlement)) - replayedGrant, err := replayed.PebbleEngine().GetGrantRecord(ctx, grant.GetId()) + replayedGrant, err := replayed.Expansion().PebbleEngine().GetGrantRecord(ctx, grant.GetId()) require.NoError(t, err) - directGrant, err := direct.PebbleEngine().GetGrantRecord(ctx, grant.GetId()) + directGrant, err := direct.Expansion().PebbleEngine().GetGrantRecord(ctx, grant.GetId()) require.NoError(t, err) require.True(t, proto.Equal(directGrant, replayedGrant)) @@ -409,7 +409,7 @@ func TestVerificationReplayMatchesDirectTypedMaterialization(t *testing.T) { GrantByNeedsExpansionLowerBound(), GrantBySourceScopeLowerBound(), } { - require.Equal(t, countKeys(t, direct.PebbleEngine(), prefix), countKeys(t, replayed.PebbleEngine(), prefix), + require.Equal(t, countKeys(t, direct.Expansion().PebbleEngine(), prefix), countKeys(t, replayed.Expansion().PebbleEngine(), prefix), "replay and direct materialization disagree for index prefix %x", prefix) } } @@ -431,12 +431,12 @@ func TestVerificationEntitlementOverlayPreservesDistinctStructuralIdentities(t * Resource: oldResource, }.Build() require.NoError(t, prev.PutEntitlements(sourcecache.WithScope(ctx, "scope-a"), oldEntitlement)) - sealReplaySource(ctx, t, prev.PebbleEngine(), sourcecache.RowKindEntitlements, "scope-a") + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), sourcecache.RowKindEntitlements, "scope-a") cur := newAdapter(t) _, err = cur.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - _, err = cur.PebbleEngine().ReplaySourceCacheEntitlements(ctx, prev.PebbleEngine(), "scope-a") + _, err = cur.Expansion().PebbleEngine().ReplaySourceCacheEntitlements(ctx, prev.Expansion().PebbleEngine(), "scope-a") require.NoError(t, err) newResource := v2pb.Resource_builder{ @@ -450,7 +450,7 @@ func TestVerificationEntitlementOverlayPreservesDistinctStructuralIdentities(t * require.NoError(t, cur.PutEntitlements(sourcecache.WithScope(ctx, "scope-a"), newEntitlement)) var rows []*v3.EntitlementRecord - require.NoError(t, cur.PebbleEngine().IterateEntitlements(ctx, func(rec *v3.EntitlementRecord) bool { + require.NoError(t, cur.Expansion().PebbleEngine().IterateEntitlements(ctx, func(rec *v3.EntitlementRecord) bool { rows = append(rows, rec) return true })) @@ -460,7 +460,7 @@ func TestVerificationEntitlementOverlayPreservesDistinctStructuralIdentities(t * rows[1].GetResource().GetResourceId(), } require.ElementsMatch(t, []string{"old", "new"}, resourceIDs) - require.Equal(t, 2, countKeys(t, cur.PebbleEngine(), EntitlementBySourceScopeLowerBound())) + require.Equal(t, 2, countKeys(t, cur.Expansion().PebbleEngine(), EntitlementBySourceScopeLowerBound())) } // C13: a true structural-identity collision replaces the replayed value exactly @@ -487,12 +487,12 @@ func TestVerificationEntitlementOverlayReplacesCollidingStructuralIdentity(t *te _, err := prev.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) require.NoError(t, prev.PutEntitlements(scoped, base)) - sealReplaySource(ctx, t, prev.PebbleEngine(), sourcecache.RowKindEntitlements, "scope-a") + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), sourcecache.RowKindEntitlements, "scope-a") replayed := newAdapter(t) _, err = replayed.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - _, err = replayed.PebbleEngine().ReplaySourceCacheEntitlements(ctx, prev.PebbleEngine(), "scope-a") + _, err = replayed.Expansion().PebbleEngine().ReplaySourceCacheEntitlements(ctx, prev.Expansion().PebbleEngine(), "scope-a") require.NoError(t, err) require.NoError(t, replayed.PutEntitlements(scoped, overlay)) @@ -502,16 +502,16 @@ func TestVerificationEntitlementOverlayReplacesCollidingStructuralIdentity(t *te require.NoError(t, direct.PutEntitlements(scoped, base)) require.NoError(t, direct.PutEntitlements(scoped, overlay)) - replayedRecord, err := replayed.PebbleEngine().GetEntitlementRecord(ctx, overlay.GetId()) + replayedRecord, err := replayed.Expansion().PebbleEngine().GetEntitlementRecord(ctx, overlay.GetId()) require.NoError(t, err) - directRecord, err := direct.PebbleEngine().GetEntitlementRecord(ctx, overlay.GetId()) + directRecord, err := direct.Expansion().PebbleEngine().GetEntitlementRecord(ctx, overlay.GetId()) require.NoError(t, err) replayedRecord.SetDiscoveredAt(nil) directRecord.SetDiscoveredAt(nil) require.True(t, proto.Equal(directRecord, replayedRecord)) require.Equal(t, "overlay", replayedRecord.GetDisplayName()) - require.Equal(t, 1, countKeys(t, replayed.PebbleEngine(), EntitlementBySourceScopeLowerBound())) - require.NoError(t, auditSourceScopeBiconditional(replayed.PebbleEngine())) + require.Equal(t, 1, countKeys(t, replayed.Expansion().PebbleEngine(), EntitlementBySourceScopeLowerBound())) + require.NoError(t, auditSourceScopeBiconditional(replayed.Expansion().PebbleEngine())) } // C18: replacing a completed sync must remove every source-cache-owned @@ -539,7 +539,7 @@ func TestVerificationResetRemovesSourceCacheFamilies(t *testing.T) { sourcecache.RowKindEntitlements, sourcecache.RowKindGrants, } { - require.NoError(t, a.PebbleEngine().PutSourceCacheEntry(ctx, string(kind), "scope-a", "validator-a")) + require.NoError(t, a.Expansion().PebbleEngine().PutSourceCacheEntry(ctx, string(kind), "scope-a", "validator-a")) } require.NoError(t, a.EndSync(ctx)) @@ -551,14 +551,14 @@ func TestVerificationResetRemovesSourceCacheFamilies(t *testing.T) { GrantBySourceScopeLowerBound(), SourceCacheEntryLowerBound(), } { - require.Zero(t, countKeys(t, a.PebbleEngine(), prefix), "reset leaked family prefix %x", prefix) + require.Zero(t, countKeys(t, a.Expansion().PebbleEngine(), prefix), "reset leaked family prefix %x", prefix) } for _, kind := range []sourcecache.RowKind{ sourcecache.RowKindResources, sourcecache.RowKindEntitlements, sourcecache.RowKindGrants, } { - _, err = a.PebbleEngine().GetSourceCacheEntry(ctx, string(kind), "scope-a") + _, err = a.Expansion().PebbleEngine().GetSourceCacheEntry(ctx, string(kind), "scope-a") require.ErrorIs(t, err, pebble.ErrNotFound) } } @@ -620,7 +620,7 @@ func TestVerificationReplayRejectsCorruptSourceMatrix(t *testing.T) { // the honest pre-corruption state, so the preflight // detects the corruption itself rather than a missing // manifest entry. - sealReplaySource(ctx, t, prev.PebbleEngine(), kind, "scope-a", corruption.replayScope) + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), kind, "scope-a", corruption.replayScope) var family sourceScopeAuditFamily for _, candidate := range sourceScopeAuditFamilies() { @@ -630,40 +630,40 @@ func TestVerificationReplayRejectsCorruptSourceMatrix(t *testing.T) { } } require.NotEmpty(t, family.name) - primaries, err := prev.PebbleEngine().db.NewIter(&pebble.IterOptions{ + primaries, err := prev.Expansion().PebbleEngine().db.NewIter(&pebble.IterOptions{ LowerBound: family.primaryLo, UpperBound: family.primaryHi, }) require.NoError(t, err) require.True(t, primaries.First()) primaryKey := append([]byte(nil), primaries.Key()...) require.NoError(t, primaries.Close()) - indexes, err := prev.PebbleEngine().db.NewIter(&pebble.IterOptions{ + indexes, err := prev.Expansion().PebbleEngine().db.NewIter(&pebble.IterOptions{ LowerBound: family.indexLo, UpperBound: family.indexHi, }) require.NoError(t, err) require.True(t, indexes.First()) indexKey := append([]byte(nil), indexes.Key()...) require.NoError(t, indexes.Close()) - corruption.apply(t, prev.PebbleEngine(), primaryKey, indexKey) + corruption.apply(t, prev.Expansion().PebbleEngine(), primaryKey, indexKey) - sourceBefore := dumpKeyRangeTest(t, prev.PebbleEngine(), nil, nil) + sourceBefore := dumpKeyRangeTest(t, prev.Expansion().PebbleEngine(), nil, nil) cur := newAdapter(t) _, err = cur.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) curDriver := newSourceScopeMutationDriver(t, cur, kind) require.NoError(t, curDriver.put(ctx, corruption.replayScope), "destination premise must occupy the exact scope that a preflight-ordering bug would clear") - destinationBefore := dumpKeyRangeTest(t, cur.PebbleEngine(), nil, nil) + destinationBefore := dumpKeyRangeTest(t, cur.Expansion().PebbleEngine(), nil, nil) - _, err = driver.replay(ctx, cur.PebbleEngine(), prev.PebbleEngine(), corruption.replayScope) + _, err = driver.replay(ctx, cur.Expansion().PebbleEngine(), prev.Expansion().PebbleEngine(), corruption.replayScope) require.ErrorContains(t, err, "preflight") scope, readErr := curDriver.readScope(ctx) require.NoError(t, readErr) require.Equal(t, corruption.replayScope, scope) - require.Equal(t, destinationBefore, dumpKeyRangeTest(t, cur.PebbleEngine(), nil, nil), + require.Equal(t, destinationBefore, dumpKeyRangeTest(t, cur.Expansion().PebbleEngine(), nil, nil), "source preflight rejection mutated the occupied destination") - require.NoError(t, auditSourceScopeBiconditional(cur.PebbleEngine())) - require.Equal(t, sourceBefore, dumpKeyRangeTest(t, prev.PebbleEngine(), nil, nil), + require.NoError(t, auditSourceScopeBiconditional(cur.Expansion().PebbleEngine())) + require.Equal(t, sourceBefore, dumpKeyRangeTest(t, prev.Expansion().PebbleEngine(), nil, nil), "source preflight rejection mutated source content or metadata") }) } @@ -687,12 +687,12 @@ func TestVerificationSourceScopeMutationAtomicity(t *testing.T) { _, err := fresh.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) freshDriver := newSourceScopeMutationDriver(t, fresh, kind) - fresh.PebbleEngine().db.SetRecordCommitTestHook(func() error { return injected }) + fresh.Expansion().PebbleEngine().db.SetRecordCommitTestHook(func() error { return injected }) err = freshDriver.put(ctx, "scope-a") require.ErrorIs(t, err, injected) - fresh.PebbleEngine().db.SetRecordCommitTestHook(nil) - require.Zero(t, countKeys(t, fresh.PebbleEngine(), freshDriver.indexLo)) - assertSourceScopeReplayRows(t, freshDriver, kind, fresh.PebbleEngine(), "scope-a", 0) + fresh.Expansion().PebbleEngine().db.SetRecordCommitTestHook(nil) + require.Zero(t, countKeys(t, fresh.Expansion().PebbleEngine(), freshDriver.indexLo)) + assertSourceScopeReplayRows(t, freshDriver, kind, fresh.Expansion().PebbleEngine(), "scope-a", 0) existing := newAdapter(t) _, err = existing.StartNewSync(ctx, connectorstore.SyncTypeFull, "") @@ -700,25 +700,25 @@ func TestVerificationSourceScopeMutationAtomicity(t *testing.T) { driver := newSourceScopeMutationDriver(t, existing, kind) require.NoError(t, driver.put(ctx, "scope-a")) - existing.PebbleEngine().db.SetRecordCommitTestHook(func() error { return injected }) + existing.Expansion().PebbleEngine().db.SetRecordCommitTestHook(func() error { return injected }) err = driver.put(ctx, "scope-b") require.ErrorIs(t, err, injected) - existing.PebbleEngine().db.SetRecordCommitTestHook(nil) + existing.Expansion().PebbleEngine().db.SetRecordCommitTestHook(nil) scope, err := driver.readScope(ctx) require.NoError(t, err) require.Equal(t, "scope-a", scope) - require.Equal(t, 1, countKeys(t, existing.PebbleEngine(), driver.indexLo)) - assertSourceScopeReplayRows(t, driver, kind, existing.PebbleEngine(), "scope-a", 1) - assertSourceScopeReplayRows(t, driver, kind, existing.PebbleEngine(), "scope-b", 0) + require.Equal(t, 1, countKeys(t, existing.Expansion().PebbleEngine(), driver.indexLo)) + assertSourceScopeReplayRows(t, driver, kind, existing.Expansion().PebbleEngine(), "scope-a", 1) + assertSourceScopeReplayRows(t, driver, kind, existing.Expansion().PebbleEngine(), "scope-b", 0) - existing.PebbleEngine().db.SetRecordCommitTestHook(func() error { return injected }) + existing.Expansion().PebbleEngine().db.SetRecordCommitTestHook(func() error { return injected }) err = driver.delete(ctx) require.ErrorIs(t, err, injected) - existing.PebbleEngine().db.SetRecordCommitTestHook(nil) + existing.Expansion().PebbleEngine().db.SetRecordCommitTestHook(nil) scope, err = driver.readScope(ctx) require.NoError(t, err) require.Equal(t, "scope-a", scope) - require.Equal(t, 1, countKeys(t, existing.PebbleEngine(), driver.indexLo)) + require.Equal(t, 1, countKeys(t, existing.Expansion().PebbleEngine(), driver.indexLo)) }) } } @@ -1019,7 +1019,7 @@ func TestVerificationSourceScopeAuditorMutationAdequacy(t *testing.T) { require.NoError(t, err) grant := scGrant("member", "alice", false) require.NoError(t, a.PutGrants(sourcecache.WithScope(ctx, "scope-a"), grant)) - rec, err := a.PebbleEngine().GetGrantRecord(ctx, grant.GetId()) + rec, err := a.Expansion().PebbleEngine().GetGrantRecord(ctx, grant.GetId()) require.NoError(t, err) id, err := grantIdentityFromRecord(rec) require.NoError(t, err) @@ -1028,29 +1028,29 @@ func TestVerificationSourceScopeAuditorMutationAdequacy(t *testing.T) { t.Run("healthy", func(t *testing.T) { a, _, _ := newFixture(t) - require.NoError(t, auditSourceScopeBiconditional(a.PebbleEngine())) + require.NoError(t, auditSourceScopeBiconditional(a.Expansion().PebbleEngine())) }) t.Run("missing-index", func(t *testing.T) { a, _, indexKey := newFixture(t) - require.NoError(t, a.PebbleEngine().db.UnsafeForTesting().Delete(indexKey, pebble.Sync)) - require.Error(t, auditSourceScopeBiconditional(a.PebbleEngine())) + require.NoError(t, a.Expansion().PebbleEngine().db.UnsafeForTesting().Delete(indexKey, pebble.Sync)) + require.Error(t, auditSourceScopeBiconditional(a.Expansion().PebbleEngine())) }) t.Run("orphan-index", func(t *testing.T) { a, primaryKey, _ := newFixture(t) - require.NoError(t, a.PebbleEngine().db.UnsafeForTesting().Delete(primaryKey, pebble.Sync)) - require.Error(t, auditSourceScopeBiconditional(a.PebbleEngine())) + require.NoError(t, a.Expansion().PebbleEngine().db.UnsafeForTesting().Delete(primaryKey, pebble.Sync)) + require.Error(t, auditSourceScopeBiconditional(a.Expansion().PebbleEngine())) }) t.Run("wrong-scope-index", func(t *testing.T) { a, primaryKey, _ := newFixture(t) wrongIndex, ok := rawdb.AppendBySourceScopeKeyFromPrimary(nil, primaryKey, "scope-b") require.True(t, ok) - require.NoError(t, a.PebbleEngine().db.UnsafeForTesting().Set(wrongIndex, nil, pebble.Sync)) - require.Error(t, auditSourceScopeBiconditional(a.PebbleEngine())) + require.NoError(t, a.Expansion().PebbleEngine().db.UnsafeForTesting().Set(wrongIndex, nil, pebble.Sync)) + require.Error(t, auditSourceScopeBiconditional(a.Expansion().PebbleEngine())) }) t.Run("malformed-primary", func(t *testing.T) { a, primaryKey, _ := newFixture(t) - require.NoError(t, a.PebbleEngine().db.UnsafeForTesting().Set(primaryKey, []byte("\xff"), pebble.Sync)) - require.Error(t, auditSourceScopeBiconditional(a.PebbleEngine())) + require.NoError(t, a.Expansion().PebbleEngine().db.UnsafeForTesting().Set(primaryKey, []byte("\xff"), pebble.Sync)) + require.Error(t, auditSourceScopeBiconditional(a.Expansion().PebbleEngine())) }) } @@ -1070,16 +1070,16 @@ func TestVerificationTerminalManifestReconcilerMutationAdequacy(t *testing.T) { } { driver := newSourceScopeMutationDriver(t, a, kind) require.NoError(t, driver.put(ctx, "scope-a")) - require.NoError(t, a.PebbleEngine().PutSourceCacheEntry(ctx, string(kind), "scope-a", "validator-a")) - require.NoError(t, a.PebbleEngine().PutSourceCacheEntry(ctx, string(kind), "zero-row", "validator-zero")) + require.NoError(t, a.Expansion().PebbleEngine().PutSourceCacheEntry(ctx, string(kind), "scope-a", "validator-a")) + require.NoError(t, a.Expansion().PebbleEngine().PutSourceCacheEntry(ctx, string(kind), "zero-row", "validator-zero")) } return a } t.Run("healthy including zero row manifests", func(t *testing.T) { a := newFixture(t) - require.NoError(t, auditSourceScopeBiconditional(a.PebbleEngine())) - counts, err := terminalManifestRowCounts(a.PebbleEngine()) + require.NoError(t, auditSourceScopeBiconditional(a.Expansion().PebbleEngine())) + counts, err := terminalManifestRowCounts(a.Expansion().PebbleEngine()) require.NoError(t, err) expected := make(map[terminalManifestKey]int) for _, kind := range []sourcecache.RowKind{ @@ -1096,8 +1096,8 @@ func TestVerificationTerminalManifestReconcilerMutationAdequacy(t *testing.T) { t.Run("stamped scope without manifest", func(t *testing.T) { a := newFixture(t) key := encodeSourceCacheEntryKey(string(sourcecache.RowKindGrants), "scope-a") - require.NoError(t, a.PebbleEngine().db.UnsafeForTesting().Delete(key, pebble.Sync)) - require.ErrorContains(t, auditTerminalManifestReconciliation(a.PebbleEngine()), "0 terminal manifest owners") + require.NoError(t, a.Expansion().PebbleEngine().db.UnsafeForTesting().Delete(key, pebble.Sync)) + require.ErrorContains(t, auditTerminalManifestReconciliation(a.Expansion().PebbleEngine()), "0 terminal manifest owners") }) } @@ -1115,13 +1115,13 @@ func TestVerificationTerminalManifestReconcilesOperationOutcomes(t *testing.T) { require.NoError(t, err) sourceDriver := newSourceScopeMutationDriver(t, prev, kind) require.NoError(t, sourceDriver.put(ctx, "scope-a")) - sealReplaySource(ctx, t, prev.PebbleEngine(), kind, "scope-a") + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), kind, "scope-a") current := newAdapter(t) _, err = current.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) currentDriver := newSourceScopeMutationDriver(t, current, kind) - _, err = sourceDriver.replay(ctx, current.PebbleEngine(), prev.PebbleEngine(), "scope-a") + _, err = sourceDriver.replay(ctx, current.Expansion().PebbleEngine(), prev.Expansion().PebbleEngine(), "scope-a") require.NoError(t, err) expectedRows := 1 switch outcome { @@ -1131,18 +1131,18 @@ func TestVerificationTerminalManifestReconcilesOperationOutcomes(t *testing.T) { require.NoError(t, currentDriver.delete(ctx)) expectedRows = 0 } - require.NoError(t, current.PebbleEngine().PutSourceCacheEntry( + require.NoError(t, current.Expansion().PebbleEngine().PutSourceCacheEntry( ctx, string(kind), "scope-a", "validator-a", )) - counts, err := terminalManifestRowCounts(current.PebbleEngine()) + counts, err := terminalManifestRowCounts(current.Expansion().PebbleEngine()) require.NoError(t, err) require.Equal(t, map[terminalManifestKey]int{ {kind: string(kind), scope: "scope-a"}: expectedRows, }, counts) - require.NoError(t, auditSourceScopeBiconditional(current.PebbleEngine())) + require.NoError(t, auditSourceScopeBiconditional(current.Expansion().PebbleEngine())) }) } } @@ -1187,7 +1187,7 @@ func TestVerificationReplayStatsCoherence(t *testing.T) { require.NoError(t, prev.PutResources(sourcecache.WithScope(ctx, "scope-a"), resource)) require.NoError(t, prev.PutEntitlements(sourcecache.WithScope(ctx, "scope-a"), entitlement)) require.NoError(t, prev.PutGrants(sourcecache.WithScope(ctx, "scope-a"), scGrant("member", "alice", false))) - sealReplaySourceMulti(ctx, t, prev.PebbleEngine(), map[sourcecache.RowKind][]string{ + sealReplaySourceMulti(ctx, t, prev.Expansion().PebbleEngine(), map[sourcecache.RowKind][]string{ sourcecache.RowKindResources: {"scope-a"}, sourcecache.RowKindEntitlements: {"scope-a"}, sourcecache.RowKindGrants: {"scope-a"}, @@ -1210,11 +1210,11 @@ func TestVerificationReplayStatsCoherence(t *testing.T) { require.NoError(t, cur.PutResources(sourcecache.WithScope(ctx, "scope-a"), obsoleteResource)) require.NoError(t, cur.PutEntitlements(sourcecache.WithScope(ctx, "scope-a"), obsoleteEntitlement)) require.NoError(t, cur.PutGrants(sourcecache.WithScope(ctx, "scope-a"), scGrant("obsolete", "obsolete", false))) - _, err = cur.PebbleEngine().ReplaySourceCacheResources(ctx, prev.PebbleEngine(), "scope-a") + _, err = cur.Expansion().PebbleEngine().ReplaySourceCacheResources(ctx, prev.Expansion().PebbleEngine(), "scope-a") require.NoError(t, err) - _, err = cur.PebbleEngine().ReplaySourceCacheEntitlements(ctx, prev.PebbleEngine(), "scope-a") + _, err = cur.Expansion().PebbleEngine().ReplaySourceCacheEntitlements(ctx, prev.Expansion().PebbleEngine(), "scope-a") require.NoError(t, err) - _, err = cur.PebbleEngine().ReplaySourceCacheGrants(ctx, prev.PebbleEngine(), "scope-a") + _, err = cur.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, prev.Expansion().PebbleEngine(), "scope-a") require.NoError(t, err) assertStats := func(e *Engine, label string, expected int64) { @@ -1223,7 +1223,7 @@ func TestVerificationReplayStatsCoherence(t *testing.T) { require.NoError(t, err, label) require.NoError(t, validateAllKindStats(stats, expected), label) } - assertStats(cur.PebbleEngine(), "after occupied replacement", 1) + assertStats(cur.Expansion().PebbleEngine(), "after occupied replacement", 1) overlayResource := v2pb.Resource_builder{ Id: v2pb.ResourceId_builder{ResourceType: "user", Resource: "bob"}.Build(), @@ -1238,35 +1238,35 @@ func TestVerificationReplayStatsCoherence(t *testing.T) { require.NoError(t, cur.PutResources(sourcecache.WithScope(ctx, "scope-a"), overlayResource)) require.NoError(t, cur.PutEntitlements(sourcecache.WithScope(ctx, "scope-a"), overlayEntitlement)) require.NoError(t, cur.PutGrants(sourcecache.WithScope(ctx, "scope-a"), scGrant("member", "bob", false))) - assertStats(cur.PebbleEngine(), "after overlay", 2) + assertStats(cur.Expansion().PebbleEngine(), "after overlay", 2) - deleted, err := cur.PebbleEngine().DeleteResourcesByIDsInScope( + deleted, err := cur.Expansion().PebbleEngine().DeleteResourcesByIDsInScope( ctx, "scope-a", map[string]struct{}{"bob": {}}, ) require.NoError(t, err) require.Equal(t, int64(1), deleted) - require.NoError(t, cur.PebbleEngine().DeleteEntitlementRecord(ctx, overlayEntitlement.GetId())) - deleted, err = cur.PebbleEngine().DeleteGrantsByPrincipalsInScope( + require.NoError(t, cur.Expansion().PebbleEngine().DeleteEntitlementRecord(ctx, overlayEntitlement.GetId())) + deleted, err = cur.Expansion().PebbleEngine().DeleteGrantsByPrincipalsInScope( ctx, "scope-a", map[string]struct{}{"bob": {}}, ) require.NoError(t, err) require.Equal(t, int64(1), deleted) - assertStats(cur.PebbleEngine(), "after tombstones", 1) + assertStats(cur.Expansion().PebbleEngine(), "after tombstones", 1) - _, err = cur.PebbleEngine().ReplaySourceCacheResources(ctx, prev.PebbleEngine(), "scope-a") + _, err = cur.Expansion().PebbleEngine().ReplaySourceCacheResources(ctx, prev.Expansion().PebbleEngine(), "scope-a") require.NoError(t, err) - _, err = cur.PebbleEngine().ReplaySourceCacheEntitlements(ctx, prev.PebbleEngine(), "scope-a") + _, err = cur.Expansion().PebbleEngine().ReplaySourceCacheEntitlements(ctx, prev.Expansion().PebbleEngine(), "scope-a") require.NoError(t, err) - _, err = cur.PebbleEngine().ReplaySourceCacheGrants(ctx, prev.PebbleEngine(), "scope-a") + _, err = cur.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, prev.Expansion().PebbleEngine(), "scope-a") require.NoError(t, err) - assertStats(cur.PebbleEngine(), "after retry replacement", 1) + assertStats(cur.Expansion().PebbleEngine(), "after retry replacement", 1) require.NoError(t, cur.EndSync(ctx)) - assertStats(cur.PebbleEngine(), "after seal", 1) + assertStats(cur.Expansion().PebbleEngine(), "after seal", 1) require.NoError(t, curEngine.Close()) reopened, err := Open(ctx, filepath.Join(curDir, "db")) require.NoError(t, err) @@ -1290,24 +1290,24 @@ func TestVerificationReplayCommitFailureRetryAllKinds(t *testing.T) { require.NoError(t, err) driver := newSourceScopeMutationDriver(t, prev, kind) require.NoError(t, driver.put(ctx, "scope-a")) - sealReplaySource(ctx, t, prev.PebbleEngine(), kind, "scope-a") + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), kind, "scope-a") cur := newAdapter(t) _, err = cur.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - cur.PebbleEngine().test.sourceCacheReplayCommitHook = func(_ string, _ int, _ bool) error { + cur.Expansion().PebbleEngine().test.sourceCacheReplayCommitHook = func(_ string, _ int, _ bool) error { return injected } - _, err = driver.replay(ctx, cur.PebbleEngine(), prev.PebbleEngine(), "scope-a") + _, err = driver.replay(ctx, cur.Expansion().PebbleEngine(), prev.Expansion().PebbleEngine(), "scope-a") require.ErrorIs(t, err, injected) - require.Zero(t, countKeys(t, cur.PebbleEngine(), driver.indexLo)) + require.Zero(t, countKeys(t, cur.Expansion().PebbleEngine(), driver.indexLo)) - cur.PebbleEngine().test.sourceCacheReplayCommitHook = nil - res, err := driver.replay(ctx, cur.PebbleEngine(), prev.PebbleEngine(), "scope-a") + cur.Expansion().PebbleEngine().test.sourceCacheReplayCommitHook = nil + res, err := driver.replay(ctx, cur.Expansion().PebbleEngine(), prev.Expansion().PebbleEngine(), "scope-a") require.NoError(t, err) require.Equal(t, int64(1), res.Rows) - require.Equal(t, 1, countKeys(t, cur.PebbleEngine(), driver.indexLo)) - require.NoError(t, auditSourceScopeBiconditional(cur.PebbleEngine())) + require.Equal(t, 1, countKeys(t, cur.Expansion().PebbleEngine(), driver.indexLo)) + require.NoError(t, auditSourceScopeBiconditional(cur.Expansion().PebbleEngine())) }) } } @@ -1322,33 +1322,33 @@ func TestVerificationManifestFailureDoesNotPublishClaim(t *testing.T) { require.NoError(t, err) grant := scGrant("member", "alice", false) require.NoError(t, prev.PutGrants(sourcecache.WithScope(ctx, "scope-a"), grant)) - sealReplaySource(ctx, t, prev.PebbleEngine(), sourcecache.RowKindGrants, "scope-a") + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), sourcecache.RowKindGrants, "scope-a") cur := newAdapter(t) _, err = cur.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - res, err := cur.PebbleEngine().ReplaySourceCacheGrants(ctx, prev.PebbleEngine(), "scope-a") + res, err := cur.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, prev.Expansion().PebbleEngine(), "scope-a") require.NoError(t, err) require.Equal(t, int64(1), res.Rows) injected := errors.New("verification manifest write failure") - cur.PebbleEngine().test.sourceCacheManifestWriteHook = func() error { return injected } - err = cur.PebbleEngine().PutSourceCacheEntry(ctx, string(sourcecache.RowKindGrants), "scope-a", "validator-a") + cur.Expansion().PebbleEngine().test.sourceCacheManifestWriteHook = func() error { return injected } + err = cur.Expansion().PebbleEngine().PutSourceCacheEntry(ctx, string(sourcecache.RowKindGrants), "scope-a", "validator-a") require.ErrorIs(t, err, injected) - _, err = cur.PebbleEngine().GetSourceCacheEntry(ctx, string(sourcecache.RowKindGrants), "scope-a") + _, err = cur.Expansion().PebbleEngine().GetSourceCacheEntry(ctx, string(sourcecache.RowKindGrants), "scope-a") require.ErrorIs(t, err, pebble.ErrNotFound) require.NoError(t, validateTerminalClaim(false, false)) - require.Error(t, auditTerminalManifestReconciliation(cur.PebbleEngine()), + require.Error(t, auditTerminalManifestReconciliation(cur.Expansion().PebbleEngine()), "nonterminal stamped rows without a manifest must not reconcile as terminal") - require.NoError(t, auditSourceScopeBiconditional(cur.PebbleEngine())) + require.NoError(t, auditSourceScopeBiconditional(cur.Expansion().PebbleEngine())) - cur.PebbleEngine().test.sourceCacheManifestWriteHook = nil - require.NoError(t, cur.PebbleEngine().PutSourceCacheEntry(ctx, string(sourcecache.RowKindGrants), "scope-a", "validator-a")) - entry, err := cur.PebbleEngine().GetSourceCacheEntry(ctx, string(sourcecache.RowKindGrants), "scope-a") + cur.Expansion().PebbleEngine().test.sourceCacheManifestWriteHook = nil + require.NoError(t, cur.Expansion().PebbleEngine().PutSourceCacheEntry(ctx, string(sourcecache.RowKindGrants), "scope-a", "validator-a")) + entry, err := cur.Expansion().PebbleEngine().GetSourceCacheEntry(ctx, string(sourcecache.RowKindGrants), "scope-a") require.NoError(t, err) require.NoError(t, validateTerminalClaim(true, true)) require.Equal(t, "validator-a", entry.GetCacheValidator()) - require.NoError(t, auditSourceScopeBiconditional(cur.PebbleEngine())) + require.NoError(t, auditSourceScopeBiconditional(cur.Expansion().PebbleEngine())) } // C23/C26/C27/C33 all-kind symmetry: replay preserves the exact encoded row @@ -1368,12 +1368,12 @@ func TestVerificationAllKindReplayMetadataFailureAndForwardSymmetry(t *testing.T require.NoError(t, err) driver := newSourceScopeMutationDriver(t, prev, kind) require.NoError(t, driver.put(ctx, "scope-a")) - require.NoError(t, prev.PebbleEngine().PutSourceCacheEntry(ctx, string(kind), "scope-a", "validator-a")) + require.NoError(t, prev.Expansion().PebbleEngine().PutSourceCacheEntry(ctx, string(kind), "scope-a", "validator-a")) // Seal before the byte-dump so the untouched-source comparisons // hold; the staged validator-a entry is kept (the seal only // fills absent manifest entries). - sealReplaySource(ctx, t, prev.PebbleEngine(), kind, "scope-a") - sourceBefore := dumpKeyRangeTest(t, prev.PebbleEngine(), nil, nil) + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), kind, "scope-a") + sourceBefore := dumpKeyRangeTest(t, prev.Expansion().PebbleEngine(), nil, nil) var family sourceScopeAuditFamily for _, candidate := range sourceScopeAuditFamilies() { @@ -1383,7 +1383,7 @@ func TestVerificationAllKindReplayMetadataFailureAndForwardSymmetry(t *testing.T } } require.NotEmpty(t, family.name) - sourceIter, err := prev.PebbleEngine().db.NewIter(&pebble.IterOptions{ + sourceIter, err := prev.Expansion().PebbleEngine().db.NewIter(&pebble.IterOptions{ LowerBound: family.primaryLo, UpperBound: family.primaryHi, }) @@ -1396,30 +1396,30 @@ func TestVerificationAllKindReplayMetadataFailureAndForwardSymmetry(t *testing.T _, err = readFailed.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) injected := errors.New("verification all-kind source read failure") - readFailed.PebbleEngine().test.sourceCacheReplayReadHook = func(_ string, _ int) error { return injected } - _, err = driver.replay(ctx, readFailed.PebbleEngine(), prev.PebbleEngine(), "scope-a") + readFailed.Expansion().PebbleEngine().test.sourceCacheReplayReadHook = func(_ string, _ int) error { return injected } + _, err = driver.replay(ctx, readFailed.Expansion().PebbleEngine(), prev.Expansion().PebbleEngine(), "scope-a") require.ErrorIs(t, err, injected) - require.Zero(t, countKeys(t, readFailed.PebbleEngine(), driver.indexLo)) - require.Equal(t, sourceBefore, dumpKeyRangeTest(t, prev.PebbleEngine(), nil, nil), + require.Zero(t, countKeys(t, readFailed.Expansion().PebbleEngine(), driver.indexLo)) + require.Equal(t, sourceBefore, dumpKeyRangeTest(t, prev.Expansion().PebbleEngine(), nil, nil), "source read failure mutated source state") cancelCtx, cancel := context.WithCancel(ctx) cancel() - _, err = driver.replay(cancelCtx, readFailed.PebbleEngine(), prev.PebbleEngine(), "scope-a") + _, err = driver.replay(cancelCtx, readFailed.Expansion().PebbleEngine(), prev.Expansion().PebbleEngine(), "scope-a") require.ErrorIs(t, err, context.Canceled) - require.Zero(t, countKeys(t, readFailed.PebbleEngine(), driver.indexLo)) - require.Equal(t, sourceBefore, dumpKeyRangeTest(t, prev.PebbleEngine(), nil, nil), + require.Zero(t, countKeys(t, readFailed.Expansion().PebbleEngine(), driver.indexLo)) + require.Equal(t, sourceBefore, dumpKeyRangeTest(t, prev.Expansion().PebbleEngine(), nil, nil), "cancelled replay mutated source state") - readFailed.PebbleEngine().test.sourceCacheReplayReadHook = nil - res, err := driver.replay(ctx, readFailed.PebbleEngine(), prev.PebbleEngine(), "scope-a") + readFailed.Expansion().PebbleEngine().test.sourceCacheReplayReadHook = nil + res, err := driver.replay(ctx, readFailed.Expansion().PebbleEngine(), prev.Expansion().PebbleEngine(), "scope-a") require.NoError(t, err) require.Equal(t, int64(1), res.Rows) - require.NoError(t, readFailed.PebbleEngine().PutSourceCacheEntry(ctx, string(kind), "scope-a", "validator-a")) - require.Equal(t, sourceBefore, dumpKeyRangeTest(t, prev.PebbleEngine(), nil, nil), + require.NoError(t, readFailed.Expansion().PebbleEngine().PutSourceCacheEntry(ctx, string(kind), "scope-a", "validator-a")) + require.Equal(t, sourceBefore, dumpKeyRangeTest(t, prev.Expansion().PebbleEngine(), nil, nil), "successful replay mutated source state") - replayedIter, err := readFailed.PebbleEngine().db.NewIter(&pebble.IterOptions{ + replayedIter, err := readFailed.Expansion().PebbleEngine().db.NewIter(&pebble.IterOptions{ LowerBound: family.primaryLo, UpperBound: family.primaryHi, }) @@ -1431,16 +1431,16 @@ func TestVerificationAllKindReplayMetadataFailureAndForwardSymmetry(t *testing.T // The manifest entry never rides along with replayed rows; the // test wrote it above, and the second-hop source must also be // sealed by its own counting EndSync. - sealReplaySource(ctx, t, readFailed.PebbleEngine(), kind, "scope-a") + sealReplaySource(ctx, t, readFailed.Expansion().PebbleEngine(), kind, "scope-a") next := newAdapter(t) _, err = next.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - res, err = driver.replay(ctx, next.PebbleEngine(), readFailed.PebbleEngine(), "scope-a") + res, err = driver.replay(ctx, next.Expansion().PebbleEngine(), readFailed.Expansion().PebbleEngine(), "scope-a") require.NoError(t, err) require.Equal(t, int64(1), res.Rows) - require.Equal(t, 1, countKeys(t, next.PebbleEngine(), driver.indexLo)) - require.NoError(t, auditSourceScopeBiconditional(next.PebbleEngine())) - require.Equal(t, sourceBefore, dumpKeyRangeTest(t, prev.PebbleEngine(), nil, nil), + require.Equal(t, 1, countKeys(t, next.Expansion().PebbleEngine(), driver.indexLo)) + require.NoError(t, auditSourceScopeBiconditional(next.Expansion().PebbleEngine())) + require.Equal(t, sourceBefore, dumpKeyRangeTest(t, prev.Expansion().PebbleEngine(), nil, nil), "second-hop replay mutated original source state") }) } @@ -1501,17 +1501,17 @@ func TestVerificationAllKindTimestampProvenance(t *testing.T) { return fmt.Errorf("unsupported row kind %q", kind) } } - require.NoError(t, put(prev.PebbleEngine(), sourceTimestamp)) - sealReplaySource(ctx, t, prev.PebbleEngine(), kind, "scope-a") + require.NoError(t, put(prev.Expansion().PebbleEngine(), sourceTimestamp)) + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), kind, "scope-a") replay := func(dst *Engine) (SourceCacheReplayResult, error) { switch kind { case sourcecache.RowKindResources: - return dst.ReplaySourceCacheResources(ctx, prev.PebbleEngine(), "scope-a") + return dst.ReplaySourceCacheResources(ctx, prev.Expansion().PebbleEngine(), "scope-a") case sourcecache.RowKindEntitlements: - return dst.ReplaySourceCacheEntitlements(ctx, prev.PebbleEngine(), "scope-a") + return dst.ReplaySourceCacheEntitlements(ctx, prev.Expansion().PebbleEngine(), "scope-a") case sourcecache.RowKindGrants: - return dst.ReplaySourceCacheGrants(ctx, prev.PebbleEngine(), "scope-a") + return dst.ReplaySourceCacheGrants(ctx, prev.Expansion().PebbleEngine(), "scope-a") default: return SourceCacheReplayResult{}, fmt.Errorf("unsupported row kind %q", kind) } @@ -1606,14 +1606,14 @@ func TestVerificationAllKindOutcomeReopenDurability(t *testing.T) { require.NoError(t, err) sourceDriver := newSourceScopeMutationDriver(t, prev, kind) require.NoError(t, sourceDriver.put(ctx, "scope-a")) - sealReplaySource(ctx, t, prev.PebbleEngine(), kind, "scope-a") + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), kind, "scope-a") currentEngine, currentDir := newTestEngine(t) current := NewAdapter(currentEngine) syncID, err := current.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) currentDriver := newSourceScopeMutationDriver(t, current, kind) - _, err = sourceDriver.replay(ctx, currentEngine, prev.PebbleEngine(), "scope-a") + _, err = sourceDriver.replay(ctx, currentEngine, prev.Expansion().PebbleEngine(), "scope-a") require.NoError(t, err) switch outcome { case "overlay": @@ -1650,16 +1650,16 @@ func TestVerificationSealedCountPreflightGates(t *testing.T) { _, err := prev.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) require.NoError(t, prev.PutGrants(sourcecache.WithScope(ctx, "scope-a"), scGrant("member", "alice", false))) - require.NoError(t, prev.PebbleEngine().PutSourceCacheEntry(ctx, kind, "scope-a", "etag-1")) + require.NoError(t, prev.Expansion().PebbleEngine().PutSourceCacheEntry(ctx, kind, "scope-a", "etag-1")) // Deliberately no EndSync: the entry exists but carries no count. dst := newAdapter(t) _, err = dst.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - before := dumpKeyRangeTest(t, dst.PebbleEngine(), nil, nil) - _, err = dst.PebbleEngine().ReplaySourceCacheGrants(ctx, prev.PebbleEngine(), "scope-a") + before := dumpKeyRangeTest(t, dst.Expansion().PebbleEngine(), nil, nil) + _, err = dst.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, prev.Expansion().PebbleEngine(), "scope-a") require.ErrorContains(t, err, "no sealed row count") - require.Equal(t, before, dumpKeyRangeTest(t, dst.PebbleEngine(), nil, nil)) + require.Equal(t, before, dumpKeyRangeTest(t, dst.Expansion().PebbleEngine(), nil, nil)) }) t.Run("rebind-clears-counts-and-refuses", func(t *testing.T) { @@ -1668,27 +1668,27 @@ func TestVerificationSealedCountPreflightGates(t *testing.T) { _, err := prev.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) require.NoError(t, prev.PutGrants(sourcecache.WithScope(ctx, "scope-a"), scGrant("member", "alice", false))) - rebind := sealReplaySource(ctx, t, prev.PebbleEngine(), sourcecache.RowKindGrants, "scope-a") - entry, err := prev.PebbleEngine().GetSourceCacheEntry(ctx, kind, "scope-a") + rebind := sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), sourcecache.RowKindGrants, "scope-a") + entry, err := prev.Expansion().PebbleEngine().GetSourceCacheEntry(ctx, kind, "scope-a") require.NoError(t, err) require.True(t, entry.HasRowCount(), "seal must have counted") // Rebinding admits mutations the sealed count no longer witnesses; // the clear is what keeps an unpublished rebound store fail-closed. rebind() - entry, err = prev.PebbleEngine().GetSourceCacheEntry(ctx, kind, "scope-a") + entry, err = prev.Expansion().PebbleEngine().GetSourceCacheEntry(ctx, kind, "scope-a") require.NoError(t, err) require.False(t, entry.HasRowCount(), "rebind must clear sealed counts") dst := newAdapter(t) _, err = dst.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - _, err = dst.PebbleEngine().ReplaySourceCacheGrants(ctx, prev.PebbleEngine(), "scope-a") + _, err = dst.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, prev.Expansion().PebbleEngine(), "scope-a") require.ErrorContains(t, err, "no sealed row count") // Resealing recounts: the same source becomes replayable again. - require.NoError(t, prev.PebbleEngine().EndSync(ctx)) - res, err := dst.PebbleEngine().ReplaySourceCacheGrants(ctx, prev.PebbleEngine(), "scope-a") + require.NoError(t, prev.Expansion().PebbleEngine().EndSync(ctx)) + res, err := dst.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, prev.Expansion().PebbleEngine(), "scope-a") require.NoError(t, err) require.Equal(t, int64(1), res.Rows) }) @@ -1701,23 +1701,23 @@ func TestVerificationSealedCountPreflightGates(t *testing.T) { gone := scGrant("member", "alice", false) kept := scGrant("member", "bob", false) require.NoError(t, prev.PutGrants(sourcecache.WithScope(ctx, "scope-a"), gone, kept)) - sealReplaySource(ctx, t, prev.PebbleEngine(), sourcecache.RowKindGrants, "scope-a") + sealReplaySource(ctx, t, prev.Expansion().PebbleEngine(), sourcecache.RowKindGrants, "scope-a") // Post-seal damage the walk can only catch by cardinality: the // primary keeps its stamp but its index entry disappears, so every // surviving index entry still resolves cleanly. - rec, err := prev.PebbleEngine().GetGrantRecord(ctx, gone.GetId()) + rec, err := prev.Expansion().PebbleEngine().GetGrantRecord(ctx, gone.GetId()) require.NoError(t, err) lostIdx := replayTestGrantScopeIndexKey(t, "scope-a", rec) - require.NoError(t, prev.PebbleEngine().UnsafeForTesting().Delete(lostIdx, nil)) + require.NoError(t, prev.Expansion().PebbleEngine().Expansion().UnsafeForTesting().Delete(lostIdx, nil)) dst := newAdapter(t) _, err = dst.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - before := dumpKeyRangeTest(t, dst.PebbleEngine(), nil, nil) - _, err = dst.PebbleEngine().ReplaySourceCacheGrants(ctx, prev.PebbleEngine(), "scope-a") + before := dumpKeyRangeTest(t, dst.Expansion().PebbleEngine(), nil, nil) + _, err = dst.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, prev.Expansion().PebbleEngine(), "scope-a") require.ErrorContains(t, err, "does not match sealed row count") - require.Equal(t, before, dumpKeyRangeTest(t, dst.PebbleEngine(), nil, nil)) + require.Equal(t, before, dumpKeyRangeTest(t, dst.Expansion().PebbleEngine(), nil, nil)) }) } @@ -1748,7 +1748,7 @@ func BenchmarkReplayPreflightScopeScaling(b *testing.B) { for scope, grants := range perScope { require.NoError(b, prev.PutGrants(sourcecache.WithScope(ctx, scope), grants...)) } - sealReplaySource(ctx, b, prev.PebbleEngine(), sourcecache.RowKindGrants, scopeNames...) + sealReplaySource(ctx, b, prev.Expansion().PebbleEngine(), sourcecache.RowKindGrants, scopeNames...) b.ResetTimer() for i := 0; i < b.N; i++ { @@ -1759,7 +1759,7 @@ func BenchmarkReplayPreflightScopeScaling(b *testing.B) { b.StartTimer() var rows int64 for _, scope := range scopeNames { - res, err := dst.PebbleEngine().ReplaySourceCacheGrants(ctx, prev.PebbleEngine(), scope) + res, err := dst.Expansion().PebbleEngine().ReplaySourceCacheGrants(ctx, prev.Expansion().PebbleEngine(), scope) require.NoError(b, err) rows += res.Rows } @@ -1787,7 +1787,7 @@ func BenchmarkSourceCacheSealRowCounts(b *testing.B) { a := newAdapter(b) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(b, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() syncID := e.CurrentSyncID() scopes := make([]string, scopeCount) for i := range scopes { @@ -1829,7 +1829,7 @@ func BenchmarkSourceCacheClearRowCounts(b *testing.B) { a := newAdapter(b) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(b, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() syncID := e.CurrentSyncID() for i := 0; i < scopeCount; i++ { require.NoError(b, e.PutSourceCacheEntry(ctx, string(sourcecache.RowKindGrants), fmt.Sprintf("scope-%05d", i), "bench-validator")) diff --git a/pkg/dotc1z/engine/pebble/spill_merge_test.go b/pkg/dotc1z/engine/pebble/spill_merge_test.go index 10e9ad86a..8f7212078 100644 --- a/pkg/dotc1z/engine/pebble/spill_merge_test.go +++ b/pkg/dotc1z/engine/pebble/spill_merge_test.go @@ -57,7 +57,7 @@ func writeTestSpillChunk(t *testing.T, dir, name string, entries []spillKV) stri func ingestAndDump(t *testing.T, e *Engine, sstPath string) []spillKV { t.Helper() ctx := context.Background() - require.NoError(t, e.IngestSSTs(ctx, []string{sstPath}), "IngestSSTs") + require.NoError(t, e.Expansion().IngestSSTs(ctx, []string{sstPath}), "IngestSSTs") iter, err := e.db.NewIter(&pebble.IterOptions{ LowerBound: []byte{0xFE}, UpperBound: []byte{0xFF}, @@ -270,7 +270,7 @@ func TestMergeGrantPrimaryMigrationChunksFoldsAcrossChunks(t *testing.T) { _, err = byNeedsExpansion.finalize() require.NoError(t, err) - require.NoError(t, e.IngestSSTs(ctx, []string{sstPath})) + require.NoError(t, e.Expansion().IngestSSTs(ctx, []string{sstPath})) got := map[string]*v3.GrantRecord{} require.NoError(t, e.IterateGrants(ctx, func(r *v3.GrantRecord) bool { got[r.GetExternalId()] = r diff --git a/pkg/dotc1z/engine/pebble/typed_record_ops_coverage_test.go b/pkg/dotc1z/engine/pebble/typed_record_ops_coverage_test.go index 16b7a1a03..c8403d90b 100644 --- a/pkg/dotc1z/engine/pebble/typed_record_ops_coverage_test.go +++ b/pkg/dotc1z/engine/pebble/typed_record_ops_coverage_test.go @@ -62,7 +62,7 @@ func TestPutSynthesizedGrantRecordsObligations(t *testing.T) { a := newAdapter(t) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() require.NoError(t, e.PutSynthesizedGrantRecords(ctx, []*v3.GrantRecord{ testGrantRecord("ent-A", "alice"), @@ -94,7 +94,7 @@ func TestPutSynthesizedGrantContributionsBatchObligations(t *testing.T) { a := newAdapter(t) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() dest := v3.EntitlementRef_builder{ ResourceTypeId: "app", ResourceId: "github", EntitlementId: "app:github:ent-A", @@ -136,7 +136,7 @@ func TestUnsafePutUniqueGrantRecordsObligations(t *testing.T) { a := newAdapter(t) syncID, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() expandable := testGrantRecord("ent-A", "alice") expandable.SetNeedsExpansion(true) @@ -183,7 +183,7 @@ func TestGrantDeleteMalformedValueStillCleansObligations(t *testing.T) { a := newAdapter(t) syncID, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() // A healthy grant first, sealed so digests exist; rebind for the // post-seal mutation (the partial-sync shape where digests are @@ -232,7 +232,7 @@ func TestDeleteResourceRecordObligations(t *testing.T) { a := newAdapter(t) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() child := v3.ResourceRecord_builder{ ResourceTypeId: "group", @@ -267,7 +267,7 @@ func TestDeferredMarkerArmFailureRollsBackCAS(t *testing.T) { a := newAdapter(t) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() injected := errNoRetryArmFailure e.db.SetDeferredMarkerTestHooks(func() error { return injected }, nil) @@ -324,7 +324,7 @@ func TestDeferredMarkerClearFailureKeepsAgreement(t *testing.T) { a := newAdapter(t) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() // Deferred-regime write arms the marker. require.NoError(t, e.PutSynthesizedGrantRecords(ctx, []*v3.GrantRecord{testGrantRecord("ent-A", "alice")})) diff --git a/pkg/dotc1z/engine/pebble/typed_record_ops_test.go b/pkg/dotc1z/engine/pebble/typed_record_ops_test.go index 2776919a7..7f8cbd42f 100644 --- a/pkg/dotc1z/engine/pebble/typed_record_ops_test.go +++ b/pkg/dotc1z/engine/pebble/typed_record_ops_test.go @@ -69,7 +69,7 @@ func TestTypedRecordOpsRejectWrongFamilyAndMalformedKeys(t *testing.T) { a := newAdapter(t) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() grantKey := encodeGrantIdentityKey(grantIdentity{ entitlement: entitlementIdentity{resourceTypeID: "app", resourceID: "github", stripped: true, tail: "member"}, @@ -126,7 +126,7 @@ func TestExpandedGrantOverwriteThroughSeal(t *testing.T) { a := newAdapter(t) _, err := a.StartNewSync(ctx, connectorstore.SyncTypeFull, "") require.NoError(t, err) - e := a.PebbleEngine() + e := a.Expansion().PebbleEngine() // Inline write with the expandable annotation → needs_expansion=true // row + index entry via the inline regime. diff --git a/pkg/dotc1z/ledger_sync_crash_test.go b/pkg/dotc1z/ledger_sync_crash_test.go index f52aa422d..33b8c24b3 100644 --- a/pkg/dotc1z/ledger_sync_crash_test.go +++ b/pkg/dotc1z/ledger_sync_crash_test.go @@ -229,7 +229,7 @@ func runDurableSync(t *testing.T, fs vfs.FS, dir, mode string, workers int, cutO } require.NoError(t, err) result.data = make(map[string]string) - it, err := e.NewIter(nil) + it, err := e.Expansion().NewIter(nil) require.NoError(t, err) for it.First(); it.Valid(); it.Next() { key := it.Key() @@ -577,7 +577,7 @@ func TestPublicLedgerDisposedFilesCompact(t *testing.T) { e, ok := engine.AsEngine(store) require.True(t, ok) data := make(map[string]string) - it, err := e.NewIter(nil) + it, err := e.Expansion().NewIter(nil) require.NoError(t, err) for it.First(); it.Valid(); it.Next() { key := it.Key() diff --git a/pkg/dotc1z/pebble_store.go b/pkg/dotc1z/pebble_store.go index 39c375c0c..b06ca3bc4 100644 --- a/pkg/dotc1z/pebble_store.go +++ b/pkg/dotc1z/pebble_store.go @@ -558,7 +558,7 @@ func (s *pebbleStore) RollbackExpansion(ctx context.Context, syncID string, dryR for _, o := range opts { o(cfg) } - res, err := s.Engine.RollbackExpansion(ctx, syncID, dryRun, cfg.preserveSuspect) + res, err := s.Engine.Expansion().RollbackExpansion(ctx, syncID, dryRun, cfg.preserveSuspect) switch { case errors.Is(err, pebble.ErrNothingToRollBack): return nil, fmt.Errorf("%w: %w", ErrSyncNotExpanded, err) diff --git a/pkg/dotc1z/source_cache_verification_test.go b/pkg/dotc1z/source_cache_verification_test.go index 69523dae0..508c94ca7 100644 --- a/pkg/dotc1z/source_cache_verification_test.go +++ b/pkg/dotc1z/source_cache_verification_test.go @@ -43,7 +43,7 @@ type sourceCacheVerificationSemanticState struct { func sourceCacheVerificationEngineDigest(t *testing.T, engine *enginepebble.Engine) [sha256.Size]byte { t.Helper() - iter, err := engine.NewIter(nil) + iter, err := engine.Expansion().NewIter(nil) require.NoError(t, err) defer func() { require.NoError(t, iter.Close()) }() @@ -75,7 +75,7 @@ func validateSourceCacheScopeCounts(target, neighbor, wantTarget, wantNeighbor i func countSourceCacheVerificationKeys(t *testing.T, engine *enginepebble.Engine, lower, upper []byte) int { t.Helper() - iter, err := engine.NewIter(&cockroachpebble.IterOptions{LowerBound: lower, UpperBound: upper}) + iter, err := engine.Expansion().NewIter(&cockroachpebble.IterOptions{LowerBound: lower, UpperBound: upper}) require.NoError(t, err) defer func() { require.NoError(t, iter.Close()) }() count := 0 @@ -94,7 +94,7 @@ func sourceCacheVerificationIndexKeys( t.Helper() var keys []string for _, bound := range bounds { - iter, err := engine.NewIter(&cockroachpebble.IterOptions{LowerBound: bound[0], UpperBound: bound[1]}) + iter, err := engine.Expansion().NewIter(&cockroachpebble.IterOptions{LowerBound: bound[0], UpperBound: bound[1]}) require.NoError(t, err) for iter.First(); iter.Valid(); iter.Next() { keys = append(keys, string(append([]byte(nil), iter.Key()...))) @@ -305,7 +305,7 @@ func invalidateSourceCacheVerificationManifest( value, err := proto.Marshal(rec) require.NoError(t, err) - iter, err := s.engine.NewIter(&cockroachpebble.IterOptions{ + iter, err := s.engine.Expansion().NewIter(&cockroachpebble.IterOptions{ LowerBound: enginepebble.SourceCacheEntryLowerBound(), UpperBound: enginepebble.SourceCacheEntryUpperBound(), }) @@ -313,7 +313,7 @@ func invalidateSourceCacheVerificationManifest( defer func() { require.NoError(t, iter.Close()) }() require.True(t, iter.First(), "manifest key must exist") key := append([]byte(nil), iter.Key()...) - require.NoError(t, s.engine.UnsafeForTesting().Set(key, value, nil)) + require.NoError(t, s.engine.Expansion().UnsafeForTesting().Set(key, value, nil)) } // C07/C32: a scope is replayable only when the previous artifact owns a @@ -1997,13 +1997,13 @@ func TestVerificationOrphanScopeIndexHealingPersistsAfterReopen(t *testing.T) { // Persist a corruption shape production typed writers cannot create: // the source-scope index remains but its primary row is absent. ps, engine = resume() - iter, err := engine.NewIter(&cockroachpebble.IterOptions{LowerBound: tc.primaryLo}) + iter, err := engine.Expansion().NewIter(&cockroachpebble.IterOptions{LowerBound: tc.primaryLo}) require.NoError(t, err) require.True(t, iter.First()) primaryKey := append([]byte(nil), iter.Key()...) require.True(t, bytes.HasPrefix(primaryKey, tc.primaryLo)) require.NoError(t, iter.Close()) - require.NoError(t, engine.UnsafeForTesting().Delete(primaryKey, cockroachpebble.Sync)) + require.NoError(t, engine.Expansion().UnsafeForTesting().Delete(primaryKey, cockroachpebble.Sync)) ps.MarkDirty() require.NoError(t, ps.Close(ctx)) diff --git a/pkg/dotc1z/sqlite/fixtures_test.go b/pkg/dotc1z/sqlite/fixtures_test.go index 2533cc9e8..86c397e13 100644 --- a/pkg/dotc1z/sqlite/fixtures_test.go +++ b/pkg/dotc1z/sqlite/fixtures_test.go @@ -64,7 +64,7 @@ func convertedKeyspace(t testing.TB, path string) map[string]string { defer func() { require.NoError(t, store.Close(ctx)) }() engine, ok := pebble.AsEngine(store) require.True(t, ok) - iter, err := engine.NewIter(nil) + iter, err := engine.Expansion().NewIter(nil) require.NoError(t, err) defer iter.Close() out := make(map[string]string) @@ -92,7 +92,7 @@ func digestConvertedFile(t testing.TB, path, syncID string) string { require.True(t, ok) hash := sha256.New() - iter, err := engine.NewIter(nil) + iter, err := engine.Expansion().NewIter(nil) require.NoError(t, err) defer iter.Close() for iter.First(); iter.Valid(); iter.Next() { diff --git a/pkg/dotc1z/sqlite/localtime_test.go b/pkg/dotc1z/sqlite/localtime_test.go index d6ca81162..f46dd0e64 100644 --- a/pkg/dotc1z/sqlite/localtime_test.go +++ b/pkg/dotc1z/sqlite/localtime_test.go @@ -94,7 +94,7 @@ func TestToPebbleDiscoveredAtIsAbsoluteInstant(t *testing.T) { defer func() { require.NoError(t, store.Close(ctx)) }() engine, ok := pebble.AsEngine(store) require.True(t, ok) - iter, err := engine.NewIter(nil) + iter, err := engine.Expansion().NewIter(nil) require.NoError(t, err) defer iter.Close() for iter.First(); iter.Valid(); iter.Next() { diff --git a/pkg/sync/expand/whale_pebble_expansion_test.go b/pkg/sync/expand/whale_pebble_expansion_test.go index 05b209a3d..cfc787fce 100644 --- a/pkg/sync/expand/whale_pebble_expansion_test.go +++ b/pkg/sync/expand/whale_pebble_expansion_test.go @@ -71,15 +71,15 @@ func TestRunWhalePebbleProjectionExpansion(t *testing.T) { m.Algorithm, m.DirtyGrantsWritten, m.ProjectionRowsBuilt, m.NodesReduced, m.DestinationEntitlements) } if eng, ok := enginepebble.AsEngine(store); ok { - stats := eng.ExpandWritePathStats() + stats := eng.Expansion().ExpandWritePathStats() t.Logf("expand write paths: expanded_calls=%d expanded_rows=%d synthesized_calls=%d synthesized_rows=%d", stats.ExpandedCalls, stats.ExpandedRows, stats.SynthesizedCalls, stats.SynthesizedRows) - eng.LogCompactionMetrics(ctx, "expansion complete") + eng.Expansion().LogCompactionMetrics(ctx, "expansion complete") } require.NoError(t, store.EndSync(ctx)) if eng, ok := enginepebble.AsEngine(store); ok { - eng.LogCompactionMetrics(ctx, "end sync complete") + eng.Expansion().LogCompactionMetrics(ctx, "end sync complete") } require.NoError(t, store.Close(ctx)) diff --git a/pkg/sync/ledger_fixture_test.go b/pkg/sync/ledger_fixture_test.go index 48feeb5b1..10242f1ce 100644 --- a/pkg/sync/ledger_fixture_test.go +++ b/pkg/sync/ledger_fixture_test.go @@ -194,7 +194,7 @@ type ledgerKV struct { func ledgerRawSnapshot(t *testing.T, e *engine.Engine) []ledgerKV { t.Helper() - iter, err := e.NewIter(nil) + iter, err := e.Expansion().NewIter(nil) require.NoError(t, err) defer func() { require.NoError(t, iter.Close()) }() var rows []ledgerKV diff --git a/pkg/sync/ledger_invalid_binding_test.go b/pkg/sync/ledger_invalid_binding_test.go index 05b2ef4d1..19f9ea259 100644 --- a/pkg/sync/ledger_invalid_binding_test.go +++ b/pkg/sync/ledger_invalid_binding_test.go @@ -38,7 +38,7 @@ func TestSyncInvalidBindingDoesNotCollect(t *testing.T) { raw, ok := engine.AsEngine(store) require.True(t, ok) snapshot := func() map[string]string { - it, err := raw.NewIter(nil) + it, err := raw.Expansion().NewIter(nil) require.NoError(t, err) defer func() { require.NoError(t, it.Close()) }() rows := make(map[string]string) diff --git a/pkg/sync/pebble_expansion_fastpath_test.go b/pkg/sync/pebble_expansion_fastpath_test.go index 458b46344..641253a09 100644 --- a/pkg/sync/pebble_expansion_fastpath_test.go +++ b/pkg/sync/pebble_expansion_fastpath_test.go @@ -72,7 +72,7 @@ func TestPebbleExpansionUsesSynthesizedFastPath(t *testing.T) { eng, ok := enginepebble.AsEngine(store) require.True(t, ok) - stats := eng.ExpandWritePathStats() + stats := eng.Expansion().ExpandWritePathStats() require.Greater(t, stats.SynthesizedRows, int64(0), "expected synthesized fast path rows") require.Greater(t, stats.SynthesizedCalls, int64(0), "expected synthesized fast path calls") require.Equal(t, int64(0), stats.ExpandedRows, "no base-update rows expected in this fixture") @@ -116,7 +116,7 @@ func TestPebbleExpansionSplitsSynthesizedAndUpdatePaths(t *testing.T) { eng, ok := enginepebble.AsEngine(store) require.True(t, ok) - stats := eng.ExpandWritePathStats() + stats := eng.Expansion().ExpandWritePathStats() require.Greater(t, stats.SynthesizedRows, int64(0), "alice should synthesize") require.Greater(t, stats.ExpandedRows, int64(0), "bob base grant should update") } diff --git a/pkg/synccompactor/compactor_grant_digest_test.go b/pkg/synccompactor/compactor_grant_digest_test.go index 5b54a3345..bd8327eda 100644 --- a/pkg/synccompactor/compactor_grant_digest_test.go +++ b/pkg/synccompactor/compactor_grant_digest_test.go @@ -409,7 +409,7 @@ func TestCompactPebbleFoldRebuildsDigestsWhenBaseHasNone(t *testing.T) { // in [lo, hi). func requireEmptyKeyRange(t testing.TB, eng *enginepkg.Engine, lo, hi []byte, what string) { t.Helper() - iter, err := eng.NewIter(&pebble.IterOptions{LowerBound: lo, UpperBound: hi}) + iter, err := eng.Expansion().NewIter(&pebble.IterOptions{LowerBound: lo, UpperBound: hi}) require.NoError(t, err) defer iter.Close() require.False(t, iter.First(), "%s keyspace must be empty, found key %x", what, iter.Key()) diff --git a/pkg/synccompactor/compactor_pebble_test.go b/pkg/synccompactor/compactor_pebble_test.go index 37dfd1e30..5e9af2f75 100644 --- a/pkg/synccompactor/compactor_pebble_test.go +++ b/pkg/synccompactor/compactor_pebble_test.go @@ -109,7 +109,7 @@ func countPebbleAssets(t *testing.T, ctx context.Context, path string) int { defer w.Close(ctx) eng, ok := enginepkg.AsEngine(w) require.True(t, ok, "store at %s is not a pebble engine", path) - it, err := eng.NewIter(&pebble.IterOptions{ + it, err := eng.Expansion().NewIter(&pebble.IterOptions{ LowerBound: enginepkg.AssetLowerBound(), UpperBound: enginepkg.AssetUpperBound(), }) @@ -298,7 +298,7 @@ func buildPebbleSourceCacheInput(t *testing.T, ctx context.Context, path string) func countPebbleRange(t *testing.T, eng *enginepkg.Engine, lower, upper []byte) int { t.Helper() - iter, err := eng.NewIter(&pebble.IterOptions{LowerBound: lower, UpperBound: upper}) + iter, err := eng.Expansion().NewIter(&pebble.IterOptions{LowerBound: lower, UpperBound: upper}) require.NoError(t, err) defer func() { require.NoError(t, iter.Close()) }() count := 0 @@ -844,7 +844,7 @@ func deletePebbleStatsSidecar(t testing.TB, ctx context.Context, path string) { require.NoError(t, err) eng, ok := enginepkg.AsEngine(w) require.True(t, ok) - iter, err := eng.NewIter(&pebble.IterOptions{ + iter, err := eng.Expansion().NewIter(&pebble.IterOptions{ LowerBound: enginepkg.SyncStatsSidecarLowerBound(), UpperBound: enginepkg.SyncStatsSidecarUpperBound(), }) @@ -856,7 +856,7 @@ func deletePebbleStatsSidecar(t testing.TB, ctx context.Context, path string) { require.NoError(t, iter.Error()) require.NoError(t, iter.Close()) for _, key := range keys { - require.NoError(t, eng.UnsafeForTesting().Delete(key, nil)) + require.NoError(t, eng.Expansion().UnsafeForTesting().Delete(key, nil)) } require.NoError(t, w.Close(ctx)) } diff --git a/pkg/synccompactor/incremental_expansion_test.go b/pkg/synccompactor/incremental_expansion_test.go index 6f85e6a5f..0ab4ba9c8 100644 --- a/pkg/synccompactor/incremental_expansion_test.go +++ b/pkg/synccompactor/incremental_expansion_test.go @@ -832,7 +832,7 @@ func TestCompactor_IncrementalSealedArtifactLifecycle(t *testing.T) { // (2) by_principal index is populated and covers sam (written incrementally). eng, ok := enginepkg.AsEngine(store) require.True(t, ok, "expected a pebble engine") - it, err := eng.NewIter(&pebble.IterOptions{ + it, err := eng.Expansion().NewIter(&pebble.IterOptions{ LowerBound: enginepkg.GrantByPrincipalLowerBound(), UpperBound: enginepkg.GrantByPrincipalUpperBound(), }) diff --git a/pkg/synccompactor/pebble/compactor.go b/pkg/synccompactor/pebble/compactor.go index 12be1cb13..8409ae3e1 100644 --- a/pkg/synccompactor/pebble/compactor.go +++ b/pkg/synccompactor/pebble/compactor.go @@ -122,7 +122,7 @@ func (c *Compactor) Compact(ctx context.Context, source *enginepkg.Engine, syncI if err := ctx.Err(); err != nil { return err } - iter, err := source.NewIter(&pebble.IterOptions{ + iter, err := source.Expansion().NewIter(&pebble.IterOptions{ LowerBound: plan.lower, UpperBound: plan.upper, }) @@ -148,7 +148,7 @@ func (c *Compactor) Compact(ctx context.Context, source *enginepkg.Engine, syncI // with zero SSTs (the ingest must reference at least one // file), so we fall back to a RangeDelete + Flush. _ = os.Remove(sstPath) - if err := c.base.DropKeyRange(plan.lower, plan.upper, pebble.Sync); err != nil { + if err := c.base.Expansion().DropKeyRange(plan.lower, plan.upper, pebble.Sync); err != nil { return fmt.Errorf("compact: excise-only DeleteRange for %s: %w", plan.name, err) } continue @@ -180,7 +180,7 @@ func (c *Compactor) Compact(ctx context.Context, source *enginepkg.Engine, syncI if err := ctx.Err(); err != nil { return err } - if err := c.base.ReplaceRangeWithSSTs(ctx, []string{p.sstPath}, p.exciseSpan); err != nil { + if err := c.base.Expansion().ReplaceRangeWithSSTs(ctx, []string{p.sstPath}, p.exciseSpan); err != nil { return fmt.Errorf("compact: IngestAndExcise: %w", err) } } diff --git a/pkg/synccompactor/pebble/kway.go b/pkg/synccompactor/pebble/kway.go index 46b66e2c7..13f42e1ca 100644 --- a/pkg/synccompactor/pebble/kway.go +++ b/pkg/synccompactor/pebble/kway.go @@ -615,7 +615,7 @@ func mergeSourceBucketToRunSection( lower, upper := bucket.syncRange() h := &directHeap{} for i, source := range sources { - iter, err := source.engine.NewIter(&cpebble.IterOptions{LowerBound: lower, UpperBound: upper}) + iter, err := source.engine.Expansion().NewIter(&cpebble.IterOptions{LowerBound: lower, UpperBound: upper}) if err != nil { return err } @@ -701,7 +701,7 @@ func materializeSourceBucketToPebble( lower, upper := bucket.syncRange() h := &directHeap{} for i, source := range sources { - iter, err := source.engine.NewIter(&cpebble.IterOptions{LowerBound: lower, UpperBound: upper}) + iter, err := source.engine.Expansion().NewIter(&cpebble.IterOptions{LowerBound: lower, UpperBound: upper}) if err != nil { return err } @@ -772,7 +772,7 @@ func materializeSourceBucketToPebble( } primarySuccess = true defer func() { _ = os.Remove(primaryPath) }() - if err := dest.IngestSSTs(ctx, []string{primaryPath}); err != nil { + if err := dest.Expansion().IngestSSTs(ctx, []string{primaryPath}); err != nil { return fmt.Errorf("ingest direct primary %s: %w", bucket.name, err) } dest.InvalidateBareIDLookups() @@ -1280,7 +1280,7 @@ func materializeRunFileBucket(ctx context.Context, dest *enginepkg.Engine, tmpDi } primarySuccess = true defer func() { _ = os.Remove(primaryPath) }() - if err := dest.IngestSSTs(ctx, []string{primaryPath}); err != nil { + if err := dest.Expansion().IngestSSTs(ctx, []string{primaryPath}); err != nil { return fmt.Errorf("ingest primary %s: %w", bucket.name, err) } dest.InvalidateBareIDLookups() @@ -1401,7 +1401,7 @@ func (w *indexRunWriter) closeSortAndIngest(ctx context.Context, dest *enginepkg return err } defer func() { _ = os.Remove(sstPath) }() - if err := dest.IngestSSTs(ctx, []string{sstPath}); err != nil { + if err := dest.Expansion().IngestSSTs(ctx, []string{sstPath}); err != nil { return fmt.Errorf("ingest index %s: %w", w.name, err) } dest.InvalidateBareIDLookups() diff --git a/pkg/synccompactor/pebble/merge.go b/pkg/synccompactor/pebble/merge.go index d48eaf739..f6dea69e8 100644 --- a/pkg/synccompactor/pebble/merge.go +++ b/pkg/synccompactor/pebble/merge.go @@ -260,13 +260,13 @@ func mergeBucketRawIfNewerWithCommitFailure( ) (FoldStats, error) { var stats FoldStats lower, upper := bucket.syncRange() - iter, err := src.NewIter(&pebble.IterOptions{LowerBound: lower, UpperBound: upper}) + iter, err := src.Expansion().NewIter(&pebble.IterOptions{LowerBound: lower, UpperBound: upper}) if err != nil { return stats, err } defer func() { _ = iter.Close() }() - batch := dest.NewFoldBatch() + batch := dest.Expansion().NewFoldBatch() defer func() { _ = batch.Close() }() pending := 0 var scratch rawIndexScratch @@ -289,7 +289,7 @@ func mergeBucketRawIfNewerWithCommitFailure( return err } _ = batch.Close() - batch = dest.NewFoldBatch() + batch = dest.Expansion().NewFoldBatch() pending = 0 return nil } @@ -302,7 +302,7 @@ func mergeBucketRawIfNewerWithCommitFailure( // Point-read the committed incumbent. Safe against the pending // batch: a sync holds one record per key, so no key repeats // within this loop, and earlier sources were fully flushed. - oldVal, closer, getErr := dest.Get(key) + oldVal, closer, getErr := dest.Expansion().Get(key) switch { case getErr == nil: if bytes.Equal(oldVal, value) { diff --git a/pkg/synccompactor/pebble/merge_test.go b/pkg/synccompactor/pebble/merge_test.go index aeca9de3e..a4b83f319 100644 --- a/pkg/synccompactor/pebble/merge_test.go +++ b/pkg/synccompactor/pebble/merge_test.go @@ -296,7 +296,7 @@ func sumBucketRawBytes(t *testing.T, eng *enginepkg.Engine, bucket bucketSpec) i ranges := append([][2][]byte{{lo, hi}}, bucketIndexRanges(bucket)...) var total int64 for _, r := range ranges { - iter, err := eng.NewIter(&cpebble.IterOptions{LowerBound: r[0], UpperBound: r[1]}) + iter, err := eng.Expansion().NewIter(&cpebble.IterOptions{LowerBound: r[0], UpperBound: r[1]}) require.NoError(t, err) for iter.First(); iter.Valid(); iter.Next() { total += int64(len(iter.Key())) + int64(len(iter.Value())) diff --git a/pkg/synccompactor/pebble/overlay.go b/pkg/synccompactor/pebble/overlay.go index b0aae000f..0c54007e8 100644 --- a/pkg/synccompactor/pebble/overlay.go +++ b/pkg/synccompactor/pebble/overlay.go @@ -366,7 +366,7 @@ func overlayRestartBucketWithCommitFailure( return err } writer.discard() - b := dest.NewFoldBatch() + b := dest.Expansion().NewFoldBatch() defer func() { _ = b.Close() }() lo, hi := bucket.syncRange() if err := b.DeleteRange(lo, hi); err != nil { @@ -952,7 +952,7 @@ func overlaySinglePassBucket( hardLimit int64, ) error { lower, upper := bucket.syncRange() - iter, err := source.NewIter(&cpebble.IterOptions{LowerBound: lower, UpperBound: upper}) + iter, err := source.Expansion().NewIter(&cpebble.IterOptions{LowerBound: lower, UpperBound: upper}) if err != nil { return err } @@ -1030,7 +1030,7 @@ func overlayWholeSourceWorthIt(ctx context.Context, source sourceHandle, bucket } } lower, upper := bucket.syncRange() - iter, err := source.engine.NewIter(&cpebble.IterOptions{LowerBound: lower, UpperBound: upper}) + iter, err := source.engine.Expansion().NewIter(&cpebble.IterOptions{LowerBound: lower, UpperBound: upper}) if err != nil { return false, err } @@ -1078,7 +1078,7 @@ func overlayMaterializeWholeSourceBucketSST( stats *mergeStatsAccumulator, ) error { lower, upper := bucket.syncRange() - iter, err := source.NewIter(&cpebble.IterOptions{LowerBound: lower, UpperBound: upper}) + iter, err := source.Expansion().NewIter(&cpebble.IterOptions{LowerBound: lower, UpperBound: upper}) if err != nil { return err } @@ -1150,7 +1150,7 @@ func overlayMaterializeWholeSourceBucketSST( primarySuccess = true defer func() { _ = os.Remove(primaryPath) }() if wrotePrimary { - if err := dest.IngestSSTs(ctx, []string{primaryPath}); err != nil { + if err := dest.Expansion().IngestSSTs(ctx, []string{primaryPath}); err != nil { return fmt.Errorf("overlay merge: ingest whole primary %s: %w", bucket.name, err) } dest.InvalidateBareIDLookups() @@ -1243,7 +1243,7 @@ func overlayCopySourceIndexesSST( if !wrote { return nil } - if err := dest.IngestSSTs(ctx, []string{sstPath}); err != nil { + if err := dest.Expansion().IngestSSTs(ctx, []string{sstPath}); err != nil { return fmt.Errorf("overlay merge: ingest whole index %s: %w", bucket.name, err) } dest.InvalidateBareIDLookups() @@ -1260,7 +1260,7 @@ func copyIndexRangeFiltered( w *sstBuilder, wrote *bool, ) error { - iter, err := source.NewIter(&cpebble.IterOptions{LowerBound: lower, UpperBound: upper}) + iter, err := source.Expansion().NewIter(&cpebble.IterOptions{LowerBound: lower, UpperBound: upper}) if err != nil { return err } @@ -1643,8 +1643,8 @@ func newOverlayBucketRawWriter(dest *enginepkg.Engine, bucket bucketSpec, stats dest: dest, bucket: bucket, chunkSize: chunkSize, - primary: dest.NewFoldBatch(), - index: dest.NewFoldBatch(), + primary: dest.Expansion().NewFoldBatch(), + index: dest.Expansion().NewFoldBatch(), stats: stats, } } @@ -1693,7 +1693,7 @@ func (w *overlayBucketRawWriter) replaceRaw(ctx context.Context, bucket bucketSp if err := w.flush(ctx); err != nil { return err } - oldVal, closer, err := w.dest.Get(destKey) + oldVal, closer, err := w.dest.Expansion().Get(destKey) if err != nil { if errors.Is(err, cpebble.ErrNotFound) { // Seen-set hash collision: the admitted record was a different @@ -1782,8 +1782,8 @@ func (w *overlayBucketRawWriter) flushWithCommitFailure(ctx context.Context, bef // error returns above, cleanup() closes whatever is still held.) _ = w.primary.Close() _ = w.index.Close() - w.primary = w.dest.NewFoldBatch() - w.index = w.dest.NewFoldBatch() + w.primary = w.dest.Expansion().NewFoldBatch() + w.index = w.dest.Expansion().NewFoldBatch() w.count = 0 return nil } @@ -1815,7 +1815,7 @@ func (w *overlayBucketRawWriter) discard() { if w.index != nil { _ = w.index.Close() } - w.primary = w.dest.NewFoldBatch() - w.index = w.dest.NewFoldBatch() + w.primary = w.dest.Expansion().NewFoldBatch() + w.index = w.dest.Expansion().NewFoldBatch() w.count = 0 } diff --git a/pkg/synccompactor/pebble/overlay_degradation_test.go b/pkg/synccompactor/pebble/overlay_degradation_test.go index 87b1c72f4..7f2a3ef8b 100644 --- a/pkg/synccompactor/pebble/overlay_degradation_test.go +++ b/pkg/synccompactor/pebble/overlay_degradation_test.go @@ -92,7 +92,7 @@ func dumpDestSyncContents(t *testing.T, e *enginepkg.Engine) map[string]string { lo, hi := bucket.syncRange() ranges := append([][2][]byte{{lo, hi}}, bucketIndexRanges(bucket)...) for _, r := range ranges { - iter, err := e.NewIter(&cpebble.IterOptions{LowerBound: r[0], UpperBound: r[1]}) + iter, err := e.Expansion().NewIter(&cpebble.IterOptions{LowerBound: r[0], UpperBound: r[1]}) require.NoError(t, err) for iter.First(); iter.Valid(); iter.Next() { out[string(iter.Key())] = string(iter.Value()) diff --git a/pkg/synccompactor/pebble/overlay_index_copy_test.go b/pkg/synccompactor/pebble/overlay_index_copy_test.go index 76fa60a1c..fcb88e71b 100644 --- a/pkg/synccompactor/pebble/overlay_index_copy_test.go +++ b/pkg/synccompactor/pebble/overlay_index_copy_test.go @@ -242,7 +242,7 @@ func assertIndexesMatchDerived(t *testing.T, ctx context.Context, dest *enginepk var stored [][]byte for _, bucket := range allBuckets() { for _, r := range bucketIndexRanges(bucket) { - iter, err := dest.NewIter(&cpebble.IterOptions{LowerBound: r[0], UpperBound: r[1]}) + iter, err := dest.Expansion().NewIter(&cpebble.IterOptions{LowerBound: r[0], UpperBound: r[1]}) require.NoError(t, err) for iter.First(); iter.Valid(); iter.Next() { stored = append(stored, append([]byte(nil), iter.Key()...)) diff --git a/pkg/synccompactor/prodscale_scan_test.go b/pkg/synccompactor/prodscale_scan_test.go index bf921c68a..f38e21e0a 100644 --- a/pkg/synccompactor/prodscale_scan_test.go +++ b/pkg/synccompactor/prodscale_scan_test.go @@ -70,7 +70,7 @@ func TestProdScaleScanVsFoldDebt(t *testing.T) { // files present; TablesCount is accurate in both modes. (The // fold's production debt gate reads metrics from a writable // open, where Sublevels is correct.) - l0Files := eng.Metrics().Levels[0].TablesCount + l0Files := eng.Expansion().Metrics().Levels[0].TablesCount start := time.Now() count := 0