You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Greenfield Ingestion Architecture Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.
Goal: Rebuild ingestion around typed run-scoped observations, set-based PostgreSQL merges, and delta-driven downstream work so a tenant with 4,300 devices, 9,000 vulnerabilities, and 230,000 software observations completes in minutes instead of hours.
Architecture: Treat source payloads as append-only observations loaded with COPY, then update compact current-state tables through SQL merge services. Split source software identity, canonical software product, and software release so 230,000 observed source rows do not become 230,000 remediation products unless they are genuinely distinct products. Make exposure, episode, projection, and enrichment phases operate on run-scoped touched IDs instead of full-tenant scans.
Tech Stack: .NET 10, EF Core with Npgsql, PostgreSQL 16, Npgsql binary COPY, xunit, FluentAssertions, Testcontainers.PostgreSql.
Non-Goals
No legacy schema compatibility.
No migration of existing production data.
No support for the old polymorphic StagedDevices ingestion path after cutover.
No EF row-object loops on the hot path.
Target Stage Budgets
For the guiding workload:
Device and software observation load: under 2 minutes.
Vulnerability and source exposure load: under 2 minutes.
Current-state merge: under 5 minutes.
Exposure state and episode sync: under 5 minutes.
Software projection and enrichment enqueue: under 3 minutes.
Total ingestion: under 20 minutes on local Docker Postgres; lower on production-class Postgres.
File Structure
Create focused files rather than expanding IngestionService.cs further.
Create tests in tests/PatchHound.Tests/Infrastructure/IngestionV2/IngestionV2SchemaTests.cs
Step 1: Write the failing schema test
[Collection(PostgresCollection.Name)]publicsealedclassIngestionV2SchemaTests{privatereadonlyPostgresFixture_fx;publicIngestionV2SchemaTests(PostgresFixturefx)=>_fx=fx;[Fact]publicasyncTaskObservation_tables_have_expected_uniques(){awaitusingvardb=_fx.CreateDbContext();varindexes=awaitdb.Database.SqlQueryRaw<string>(""" SELECT indexname FROM pg_indexes WHERE schemaname = 'public' AND tablename IN ( 'RawDeviceObservations', 'RawSoftwareObservations', 'RawInstallationObservations', 'RawVulnerabilityObservations', 'RawExposureObservations', 'SoftwareSourceIdentities', 'SoftwareReleases', 'IngestionRunDeltas') ORDER BY indexname """).ToListAsync();indexes.Should().Contain("UX_RawDeviceObservations_Run_Source_ExternalId");indexes.Should().Contain("UX_RawSoftwareObservations_Run_Source_ExternalId");indexes.Should().Contain("UX_RawInstallationObservations_Run_Device_Software");indexes.Should().Contain("UX_RawVulnerabilityObservations_Run_Source_ExternalId");indexes.Should().Contain("UX_RawExposureObservations_Run_Device_Vulnerability_Software");indexes.Should().Contain("UX_SoftwareSourceIdentities_Source_ExternalId");indexes.Should().Contain("UX_SoftwareReleases_Product_Version");indexes.Should().Contain("UX_IngestionRunDeltas_Run_Kind_Id");}}
Step 2: Run the failing test
Run: dotnet test PatchHound.slnx --filter FullyQualifiedName~IngestionV2SchemaTests -v minimal
Expected: compile failure because the new entities and DbSets do not exist.
Step 3: Add core entities
Add immutable factory-style entities with explicit max-length validation:
Use RawDeviceObservations as source and upsert Devices with ON CONFLICT (TenantId, SourceSystemId, ExternalId).
Step 3: Implement MergeInstallationsAsync
Join raw installations to:
current Devices by (TenantId, SourceSystemId, DeviceExternalId),
SoftwareSourceIdentities by (SourceSystemId, SoftwareExternalId),
SoftwareReleases by source identity release.
Upsert InstalledSoftware. Store SoftwareSourceIdentityId and SoftwareReleaseId if new columns are introduced; otherwise store the resolved product and version.
Step 4: Implement stale installation resolution
For devices touched in the run, mark prior installations from the same source inactive when absent from RawInstallationObservations for the run. Prefer an IsActive flag over deleting rows.
Step 5: Run tests
Run: dotnet test PatchHound.slnx --filter FullyQualifiedName~PostgresIngestionStateMergerInstallationTests -v minimal
Expected: PASS.
Step 6: Commit
git add src/PatchHound.Infrastructure/Services/IngestionV2 tests/PatchHound.Tests/Infrastructure/IngestionV2
git commit -m "feat: merge devices and installations from observations"
Task 5: Bulk Merge Vulnerabilities and Direct Exposure Facts
vulnerabilities in IngestionRunDeltas for the run,
devices touched in the run.
Do not scan every tenant install.
Step 3: Move version matching into SQL
Add normalized version columns to SoftwareReleases and applicability bounds. Use a PostgreSQL function or generated sortable version key. The function must return NULL for non-comparable versions and fall back to direct source exposure facts for those rows.
Step 4: Run tests
Run: dotnet test PatchHound.slnx --filter FullyQualifiedName~PostgresApplicabilityExposureDeriverTests -v minimal
Expected: PASS.
Step 5: Commit
git add src/PatchHound.Infrastructure/Services/IngestionV2 tests/PatchHound.Tests/Infrastructure/IngestionV2
git commit -m "feat: derive applicability exposures from run deltas"
Step 3: Remove legacy staging dependencies from DI
Remove registrations that are no longer used by ingestion. Keep services only if other non-ingestion features still need them.
Step 4: Run full backend tests
Run: dotnet test PatchHound.slnx -v minimal
Expected: PASS.
Step 5: Run target benchmark
Run the realistic target benchmark from Task 11.
Expected: under 20 minutes locally.
Step 6: Commit
git add src tests docs benchmarks
git commit -m "feat: cut over ingestion to v2 pipeline"
Performance Verification Checklist
Before merging:
dotnet build PatchHound.slnx passes.
dotnet test PatchHound.slnx -v minimal passes.
Realistic benchmark completes under target.
Each SQL-heavy stage has row counts and elapsed timings.
EXPLAIN (ANALYZE, BUFFERS) has been captured for any stage over 60 seconds.
No hot-path stage loads all tenant InstalledSoftware, all tenant SoftwareTenantRecords, or all tenant DeviceVulnerabilityExposures unless that table is already constrained by run deltas.
gitnexus_detect_changes(scope: "all") reports only expected ingestion, schema, benchmark, and test areas.
Rollout Notes
Because this is greenfield and does not preserve legacy compatibility, rollout should be branch-level rather than feature-flag-level:
Finish all tasks on a dedicated branch.
Recreate local/dev databases from scratch.
Run the realistic benchmark.
Run one real Defender ingestion against a disposable tenant.
Only then remove old migrations or squash schema if desired.
Greenfield Ingestion Architecture Implementation Plan
Goal: Rebuild ingestion around typed run-scoped observations, set-based PostgreSQL merges, and delta-driven downstream work so a tenant with 4,300 devices, 9,000 vulnerabilities, and 230,000 software observations completes in minutes instead of hours.
Architecture: Treat source payloads as append-only observations loaded with
COPY, then update compact current-state tables through SQL merge services. Split source software identity, canonical software product, and software release so 230,000 observed source rows do not become 230,000 remediation products unless they are genuinely distinct products. Make exposure, episode, projection, and enrichment phases operate on run-scoped touched IDs instead of full-tenant scans.Tech Stack: .NET 10, EF Core with Npgsql, PostgreSQL 16, Npgsql binary
COPY, xunit, FluentAssertions, Testcontainers.PostgreSql.Non-Goals
StagedDevicesingestion path after cutover.Target Stage Budgets
For the guiding workload:
File Structure
Create focused files rather than expanding
IngestionService.csfurther.src/PatchHound.Core/Entities/Ingestion/SoftwareSourceIdentity.cssrc/PatchHound.Core/Entities/Ingestion/SoftwareRelease.cssrc/PatchHound.Core/Entities/Ingestion/RawDeviceObservation.cssrc/PatchHound.Core/Entities/Ingestion/RawSoftwareObservation.cssrc/PatchHound.Core/Entities/Ingestion/RawInstallationObservation.cssrc/PatchHound.Core/Entities/Ingestion/RawVulnerabilityObservation.cssrc/PatchHound.Core/Entities/Ingestion/RawExposureObservation.cssrc/PatchHound.Core/Entities/Ingestion/IngestionRunDelta.cssrc/PatchHound.Infrastructure/Data/Configurations/Ingestion/src/PatchHound.Core/Interfaces/IObservationBulkLoader.cssrc/PatchHound.Core/Interfaces/IIngestionStateMerger.cssrc/PatchHound.Core/Interfaces/IExposureStateMerger.cssrc/PatchHound.Core/Interfaces/IEpisodeStateMerger.cssrc/PatchHound.Core/Interfaces/IIncrementalSoftwareProjectionWriter.cssrc/PatchHound.Infrastructure/Services/IngestionV2/tests/PatchHound.Tests/Infrastructure/IngestionV2/src/PatchHound.Infrastructure/Data/PatchHoundDbContext.cssrc/PatchHound.Infrastructure/DependencyInjection.cssrc/PatchHound.Infrastructure/Services/IngestionService.csonly after the new path has end-to-end tests.Task 1: Add Typed Observation Schema
Files:
Create entity files under
src/PatchHound.Core/Entities/Ingestion/Create EF configurations under
src/PatchHound.Infrastructure/Data/Configurations/Ingestion/Modify
src/PatchHound.Infrastructure/Data/PatchHoundDbContext.csCreate tests in
tests/PatchHound.Tests/Infrastructure/IngestionV2/IngestionV2SchemaTests.csStep 1: Write the failing schema test
Run:
dotnet test PatchHound.slnx --filter FullyQualifiedName~IngestionV2SchemaTests -v minimalExpected: compile failure because the new entities and
DbSets do not exist.Add immutable factory-style entities with explicit max-length validation:
SoftwareSourceIdentity:Id,SourceSystemId,ExternalId,ObservedVendor,ObservedName,ObservedVersion,CanonicalProductKey,SoftwareProductId,SoftwareReleaseId,FirstSeenAt,LastSeenAt.SoftwareRelease:Id,SoftwareProductId,NormalizedVersion,RawVersion,FirstSeenAt,LastSeenAt.RawDeviceObservation: run-scoped device facts.RawSoftwareObservation: run-scoped source software facts.RawInstallationObservation: run-scoped device-to-source-software facts.RawVulnerabilityObservation: run-scoped vulnerability facts without affected asset arrays.RawExposureObservation: run-scoped direct source exposure facts.IngestionRunDelta:RunId,TenantId,Kind,EntityId.Use entity factory methods named
Create(...)and throwArgumentExceptionwhen external IDs, names, or versions exceed existing EF caps.Configure the table names and indexes:
Also add lookup indexes for:
RawInstallationObservations(IngestionRunId, TenantId, DeviceExternalId)RawInstallationObservations(IngestionRunId, TenantId, SoftwareExternalId)RawExposureObservations(IngestionRunId, TenantId, DeviceExternalId)RawExposureObservations(IngestionRunId, TenantId, VulnerabilityExternalId)SoftwareSourceIdentities(SoftwareProductId)SoftwareReleases(SoftwareProductId, NormalizedVersion)Step 5: Wire DbContext
Add
DbSet<>properties inPatchHoundDbContextand apply configurations.Ask the user to run:
Do not run
dotnet effrom an agent session.Run:
dotnet test PatchHound.slnx --filter FullyQualifiedName~IngestionV2SchemaTests -v minimalExpected: PASS.
git add src/PatchHound.Core/Entities/Ingestion src/PatchHound.Infrastructure/Data tests/PatchHound.Tests/Infrastructure/IngestionV2 git commit -m "feat: add ingestion v2 observation schema"Task 2: Add Npgsql COPY Observation Loader
Files:
Create
src/PatchHound.Core/Interfaces/IObservationBulkLoader.csCreate
src/PatchHound.Infrastructure/Services/IngestionV2/PostgresObservationBulkLoader.csModify
src/PatchHound.Infrastructure/DependencyInjection.csTest
tests/PatchHound.Tests/Infrastructure/IngestionV2/PostgresObservationBulkLoaderTests.csStep 1: Write failing bulk-loader test
Run:
dotnet test PatchHound.slnx --filter FullyQualifiedName~PostgresObservationBulkLoaderTests -v minimalExpected: compile failure for missing loader and input records.
Create compact input records in
IObservationBulkLoader.cs:Include static
Create(...)helpers on each input record to normalize empty strings and trim values.Implement one transaction per batch:
COPYinputs into temp tables.ON CONFLICT DO UPDATE.AddRange.Register
IObservationBulkLoaderas scoped inDependencyInjection.cs.Run:
dotnet test PatchHound.slnx --filter FullyQualifiedName~PostgresObservationBulkLoaderTests -v minimalExpected: PASS.
git add src/PatchHound.Core/Interfaces/IObservationBulkLoader.cs src/PatchHound.Infrastructure/Services/IngestionV2 src/PatchHound.Infrastructure/DependencyInjection.cs tests/PatchHound.Tests/Infrastructure/IngestionV2 git commit -m "feat: load ingestion observations with postgres copy"Task 3: Merge Source Software Identities and Releases
Files:
Create
src/PatchHound.Core/Interfaces/IIngestionStateMerger.csCreate
src/PatchHound.Infrastructure/Services/IngestionV2/PostgresIngestionStateMerger.csTest
tests/PatchHound.Tests/Infrastructure/IngestionV2/PostgresIngestionStateMergerSoftwareTests.csStep 1: Write failing software merge test
Run:
dotnet test PatchHound.slnx --filter FullyQualifiedName~PostgresIngestionStateMergerSoftwareTests -v minimalExpected: compile failure.
MergeSoftwareAsyncmust:SoftwareProductsfrom distinctCanonicalProductKey.SoftwareReleasesfrom(SoftwareProductId, NormalizedVersion).SoftwareSourceIdentitiesfrom(SourceSystemId, ExternalId).IngestionRunDeltas.Use
INSERT ... SELECT DISTINCT ... ON CONFLICT ... DO UPDATE.Run:
dotnet test PatchHound.slnx --filter FullyQualifiedName~PostgresIngestionStateMergerSoftwareTests -v minimalExpected: PASS.
git add src/PatchHound.Core/Interfaces/IIngestionStateMerger.cs src/PatchHound.Infrastructure/Services/IngestionV2 tests/PatchHound.Tests/Infrastructure/IngestionV2 git commit -m "feat: merge source software identities incrementally"Task 4: Merge Devices and Installations From Observations
Files:
Modify
PostgresIngestionStateMerger.csTest
tests/PatchHound.Tests/Infrastructure/IngestionV2/PostgresIngestionStateMergerInstallationTests.csStep 1: Write failing installation merge test
MergeDevicesAsyncUse
RawDeviceObservationsas source and upsertDeviceswithON CONFLICT (TenantId, SourceSystemId, ExternalId).MergeInstallationsAsyncJoin raw installations to:
Devicesby(TenantId, SourceSystemId, DeviceExternalId),SoftwareSourceIdentitiesby(SourceSystemId, SoftwareExternalId),SoftwareReleasesby source identity release.Upsert
InstalledSoftware. StoreSoftwareSourceIdentityIdandSoftwareReleaseIdif new columns are introduced; otherwise store the resolved product and version.For devices touched in the run, mark prior installations from the same source inactive when absent from
RawInstallationObservationsfor the run. Prefer anIsActiveflag over deleting rows.Run:
dotnet test PatchHound.slnx --filter FullyQualifiedName~PostgresIngestionStateMergerInstallationTests -v minimalExpected: PASS.
git add src/PatchHound.Infrastructure/Services/IngestionV2 tests/PatchHound.Tests/Infrastructure/IngestionV2 git commit -m "feat: merge devices and installations from observations"Task 5: Bulk Merge Vulnerabilities and Direct Exposure Facts
Files:
Modify
PostgresIngestionStateMerger.csCreate
src/PatchHound.Infrastructure/Services/IngestionV2/PostgresExposureStateMerger.csTest
tests/PatchHound.Tests/Infrastructure/IngestionV2/PostgresExposureStateMergerTests.csStep 1: Write failing direct exposure test
Replace per-vulnerability resolver semantics with set-based SQL:
Upsert
VulnerabilitiesfromRawVulnerabilityObservations.Reconcile references and applicability rules in separate bulk SQL.
Insert
Vulnerabilitydeltas.Step 3: Implement direct exposure merge
Upsert exposures from
RawExposureObservationsjoined to current devices, vulnerabilities, and software identities. Conflict key remains(TenantId, DeviceId, VulnerabilityId)unless greenfield schema allows(TenantId, DeviceId, VulnerabilityId, SoftwareSourceIdentityId).Resolve only open exposures for devices touched by the run and source system when no matching raw exposure exists in the current run.
Run:
dotnet test PatchHound.slnx --filter FullyQualifiedName~PostgresExposureStateMergerTests -v minimalExpected: PASS.
git add src/PatchHound.Infrastructure/Services/IngestionV2 tests/PatchHound.Tests/Infrastructure/IngestionV2 git commit -m "feat: merge direct exposure facts set-based"Task 6: Replace Full-Tenant Exposure Derivation With Delta Derivation
Files:
Create
src/PatchHound.Infrastructure/Services/IngestionV2/PostgresApplicabilityExposureDeriver.csTest
tests/PatchHound.Tests/Infrastructure/IngestionV2/PostgresApplicabilityExposureDeriverTests.csStep 1: Write failing delta derivation test
Derive exposure candidates from:
IngestionRunDeltasfor the run,IngestionRunDeltasfor the run,Do not scan every tenant install.
Add normalized version columns to
SoftwareReleasesand applicability bounds. Use a PostgreSQL function or generated sortable version key. The function must returnNULLfor non-comparable versions and fall back to direct source exposure facts for those rows.Run:
dotnet test PatchHound.slnx --filter FullyQualifiedName~PostgresApplicabilityExposureDeriverTests -v minimalExpected: PASS.
git add src/PatchHound.Infrastructure/Services/IngestionV2 tests/PatchHound.Tests/Infrastructure/IngestionV2 git commit -m "feat: derive applicability exposures from run deltas"Task 7: Make Episode Sync Set-Based
Files:
Create
src/PatchHound.Core/Interfaces/IEpisodeStateMerger.csCreate
src/PatchHound.Infrastructure/Services/IngestionV2/PostgresEpisodeStateMerger.csTest
tests/PatchHound.Tests/Infrastructure/IngestionV2/PostgresEpisodeStateMergerTests.csStep 1: Write failing episode sync test
Insert a new episode for open exposures touched by the run where no open episode exists.
Close open episodes where the corresponding exposure was resolved by the run.
Run:
dotnet test PatchHound.slnx --filter FullyQualifiedName~PostgresEpisodeStateMergerTests -v minimalExpected: PASS.
git add src/PatchHound.Core/Interfaces/IEpisodeStateMerger.cs src/PatchHound.Infrastructure/Services/IngestionV2 tests/PatchHound.Tests/Infrastructure/IngestionV2 git commit -m "feat: sync exposure episodes set-based"Task 8: Incremental Software Projection
Files:
Create
src/PatchHound.Core/Interfaces/IIncrementalSoftwareProjectionWriter.csCreate
src/PatchHound.Infrastructure/Services/IngestionV2/PostgresIncrementalSoftwareProjectionWriter.csTest
tests/PatchHound.Tests/Infrastructure/IngestionV2/PostgresIncrementalSoftwareProjectionWriterTests.csStep 1: Write failing projection test
Update/insert
SoftwareTenantRecordsonly forSoftwareProductdeltas from the run.Update/insert/deactivate
SoftwareProductInstallationsonly forInstalledSoftwaredeltas from the run.Run:
dotnet test PatchHound.slnx --filter FullyQualifiedName~PostgresIncrementalSoftwareProjectionWriterTests -v minimalExpected: PASS.
git add src/PatchHound.Core/Interfaces/IIncrementalSoftwareProjectionWriter.cs src/PatchHound.Infrastructure/Services/IngestionV2 tests/PatchHound.Tests/Infrastructure/IngestionV2 git commit -m "feat: project software incrementally"Task 9: Incremental Enrichment Queueing
Files:
Create
src/PatchHound.Infrastructure/Services/IngestionV2/IngestionV2EnrichmentPlanner.csTest
tests/PatchHound.Tests/Infrastructure/IngestionV2/IngestionV2EnrichmentPlannerTests.csStep 1: Write failing enrichment test
Read
IngestionRunDeltasfor kinds:SoftwareProductVulnerabilityOnly enqueue jobs when the entity was created or materially changed in the current run.
Run:
dotnet test PatchHound.slnx --filter FullyQualifiedName~IngestionV2EnrichmentPlannerTests -v minimalExpected: PASS.
git add src/PatchHound.Infrastructure/Services/IngestionV2 tests/PatchHound.Tests/Infrastructure/IngestionV2 git commit -m "feat: enqueue enrichment from ingestion deltas"Task 10: Add Ingestion V2 Orchestrator
Files:
Create
src/PatchHound.Infrastructure/Services/IngestionV2/IngestionV2Service.csModify
src/PatchHound.Infrastructure/DependencyInjection.csTest
tests/PatchHound.Tests/Infrastructure/IngestionV2/IngestionV2ServiceTests.csStep 1: Write failing end-to-end test
The service order is:
IObservationBulkLoader.LoadAsyncIIngestionStateMerger.MergeSoftwareAsyncIIngestionStateMerger.MergeDevicesAsyncIIngestionStateMerger.MergeInstallationsAsyncIIngestionStateMerger.MergeVulnerabilitiesAsyncIExposureStateMerger.MergeDirectExposuresAsyncPostgresApplicabilityExposureDeriver.DeriveAsyncIEpisodeStateMerger.SyncAsyncIIncrementalSoftwareProjectionWriter.SyncAsyncIngestionV2EnrichmentPlanner.EnqueueAsyncReturn and log per-stage elapsed time and row counts.
Run:
dotnet test PatchHound.slnx --filter FullyQualifiedName~IngestionV2ServiceTests -v minimalExpected: PASS.
git add src/PatchHound.Infrastructure/Services/IngestionV2 src/PatchHound.Infrastructure/DependencyInjection.cs tests/PatchHound.Tests/Infrastructure/IngestionV2 git commit -m "feat: orchestrate ingestion v2 pipeline"Task 11: Replace Benchmark With Realistic Cardinality Scenarios
Files:
Modify
benchmarks/PatchHound.IngestionBenchmark/Create
benchmarks/PatchHound.IngestionBenchmark/V2BenchmarkSeeder.csCreate
benchmarks/PatchHound.IngestionBenchmark/V2BenchmarkRunner.csStep 1: Add benchmark scenario
Add a scenario named
defender-realisticwith:devices:
4300software observations:
230000vulnerabilities:
9000direct exposure observations: configurable, default
800000Step 2: Print all stage timings
Print:
observation load
software merge
device merge
installation merge
vulnerability merge
direct exposure merge
applicability derivation
episode sync
software projection
enrichment queue
total
Step 3: Run small benchmark
Run:
Expected: completes and prints all stages.
Run:
Expected: total under 20 minutes locally. If not, capture top two slow stages and add an optimization task before cutover.
git add benchmarks/PatchHound.IngestionBenchmark git commit -m "test: add ingestion v2 realistic benchmark"Task 12: Cut Over and Remove Legacy Hot Path
Files:
Modify
src/PatchHound.Infrastructure/Services/IngestionService.csModify
src/PatchHound.Infrastructure/DependencyInjection.csDelete or quarantine old staged merge services after tests pass
Update docs
Step 1: Add integration test for production entry point
Write a test proving the existing ingestion entry point uses
IngestionV2Serviceand no longer calls:StagedDeviceMergeServiceExposureDerivationServicefull-tenant pathNormalizedSoftwareProjectionServicefull-tenant pathStep 2: Replace orchestration
Route ingestion through
IngestionV2Service.Remove registrations that are no longer used by ingestion. Keep services only if other non-ingestion features still need them.
Run:
dotnet test PatchHound.slnx -v minimalExpected: PASS.
Run the realistic target benchmark from Task 11.
Expected: under 20 minutes locally.
git add src tests docs benchmarks git commit -m "feat: cut over ingestion to v2 pipeline"Performance Verification Checklist
Before merging:
dotnet build PatchHound.slnxpasses.dotnet test PatchHound.slnx -v minimalpasses.EXPLAIN (ANALYZE, BUFFERS)has been captured for any stage over 60 seconds.InstalledSoftware, all tenantSoftwareTenantRecords, or all tenantDeviceVulnerabilityExposuresunless that table is already constrained by run deltas.gitnexus_detect_changes(scope: "all")reports only expected ingestion, schema, benchmark, and test areas.Rollout Notes
Because this is greenfield and does not preserve legacy compatibility, rollout should be branch-level rather than feature-flag-level: