Spensara Spensara Knowledge Base & Documentation Repository

Repository Docs & Knowledge Base

Consolidated Spensara documentation repository (722 Markdown & architecture documents in ../repository)
Built Word Documents (.docx): Spensara Overview (.docx) Capability Assessment (.docx) Brink POS Ingestion Analysis (.docx) The Borg Ingestion Architecture (.docx)

Implementation Specs (157)

Detailed engineering and feature implementation specifications.
implementation-specs/specs/KdsCdcIngestion/TASK4.md 18,625 bytes · Updated Oct 23, 2026 10:44 AM EDT
Raw File

TASK 4: Implement Tenant Resolution And Quarantine Boundaries

Summary

Implement the KDS CDC tenant-resolution seam that maps source customer/account and store_guid into platform company_id, tenant_id, and tenant warehouse identity before any tenant-scoped raw S3 path or Redshift load is created.

This task freezes the mapping contract, explicit resolution rules, ambiguous/missing mapping handling, and the quarantine boundary for record-local mapping failures. It also defines how unresolved records are diagnosed and replayed without leaking into tenant-scoped raw/load/curated paths.

This task does not yet implement raw S3 persistence, durable job creation, normalization, Redshift COPY, curated merges, or checkpoint replay mechanics.

Current Codebase Findings

Repository analysis shows the codebase already has tenant warehouse resolution, but it does not yet have the source-account plus store_guid mapping seam required by the KDS CDC feature.

  • backend/Kds.Ai.Warehouse/Abstractions/ITenantWarehouseResolver.cs and backend/Kds.Ai.Warehouse/Services/MetadataTenantWarehouseResolver.cs only resolve tenant_id -> tenant database using ITenantWarehouseMappingRepository.
  • backend/Kds.Ai.Data/Repositories/ITenantWarehouseMappingRepository.cs and EfTenantWarehouseMappingRepository.cs store only tenant-to-warehouse metadata: TenantId, TenantDatabase, activation state, and provisioning state.
  • backend/Kds.Ai.Data/Entities/Customer.cs and ICustomerRepository.cs expose tenant-facing customer data such as TenantId and Company, but there is no current repository that maps external KDS source account/store identity into platform company/tenant identity.
  • backend/Kds.Ai.Warehouse/Services/Synthetic/SyntheticKdsFeedService.cs and SimulatorWarehousePublisher.cs require a caller-supplied tenant id up front and then resolve warehouse from that tenant. This is opposite of KDS CDC, where source account/store must be resolved into tenant identity first.
  • backend/Kds.Ai.Integrations/Etl/StageExecutionSupport.cs, backend/Kds.Ai.Warehouse/Services/Storage/FailedPackageWriter.cs, backend/Kds.Ai.Warehouse/Models/Storage/FailedPackageWriteRequest.cs, and backend/Kds.Ai.Integrations/Etl/StageBatchFailureRequest.cs all assume failure packaging is stage-scoped and already has CompanyId, TenantId, BatchId, and PipelineStage.
  • backend/Kds.Ai.Warehouse/Services/Storage/S3EtlStorage.cs builds raw, load, curated, and failed paths with mandatory company_id and tenant_id key segments.
  • backend/Kds.Ai.Data/Models/Ingestion/CreateIngestionBatchRequest.cs requires TenantId and TenantDatabase when creating stage batches, with optional CompanyId.

These findings matter for Task 4:

  1. current warehouse mapping is downstream of tenant identity, not a replacement for source-account/store resolution,
  2. unresolved or ambiguous CDC records cannot reuse current stage-level failed-package writer directly because that writer requires tenant-scoped identity,
  3. KDS CDC needs a new explicit mapping seam plus a pre-tenant quarantine seam before Task 5 raw persistence begins.

Dependencies

  • docs/features/ETL/kds-cdc/index.md
  • docs/features/ETL/kds-cdc/implementation-breakdown.md
  • specs/KdsCdcIngestion/TASK2.md
  • specs/KdsCdcIngestion/TASK3.md

Source References

  • ../../docs/features/ETL/kds-cdc/implementation-breakdown.md#task-checklist
  • ../../docs/features/ETL/kds-cdc/index.md#stage-3-tenant-resolution
  • ../../docs/features/ETL/kds-cdc/index.md#quarantine-and-failed-packages
  • ../../docs/features/ETL/kds-cdc/index.md#supported-corner-cases
  • ../../docs/features/ETL/kds-cdc/index.md#selected-behavior-summary

Scope From Breakdown

This task covers only the fourth implementation-breakdown item:

  • map source customer/account to company_id and store_guid to tenant_id
  • block implicit or ambiguous tenant inference
  • quarantine record-local mapping failures with enough metadata for diagnosis and replay
  • ensure unresolved records never reach tenant-scoped S3 paths or Redshift loads

Files to Create

1. KDS CDC source-to-tenant resolution seam

Create a dedicated resolution seam for KDS CDC source identity.

Recommended logical location:

  • backend/Kds.Ai.Integrations/KdsCdc/Resolution/

Recommended logical types:

  • IKdsCdcTenantResolver
  • KdsCdcTenantResolver
  • KdsCdcTenantResolutionRequest
  • KdsCdcTenantResolutionResult
  • KdsCdcResolvedTenantContext

Minimum responsibilities:

  • accept source-facing identity from CDC message metadata,
  • resolve source_account_id -> company_id,
  • resolve source_account_id + store_guid -> tenant_id,
  • resolve tenant_id -> tenant database through existing warehouse resolver or repository seam,
  • return explicit success, missing-mapping, or ambiguous-mapping outcomes,
  • keep mapping logic separate from worker polling and later raw persistence logic.

2. Source mapping repository or service contract

Create a persistence-facing seam for the mapping table or mapping service described by the feature doc.

Recommended logical locations:

  • backend/Kds.Ai.Data/Repositories/
  • backend/Kds.Ai.Data/Entities/
  • backend/Kds.Ai.Data/Configurations/

Recommended logical types:

  • IKdsCdcSourceTenantMappingRepository
  • EfKdsCdcSourceTenantMappingRepository
  • KdsCdcSourceTenantMapping

Recommended fields for the mapping record:

  • source account/customer id
  • source store_guid
  • resolved company_id
  • resolved tenant_id
  • optional source system discriminator if future KDS variants exist
  • activation flag or effective state if mappings can be disabled
  • audit timestamps

Important rule:

  • mapping key must be explicit and deterministic,
  • Task 4 must not depend on fuzzy matching against customer names, company names, or inferred store ownership.

3. Pre-tenant quarantine seam

Create a quarantine seam for mapping failures that happen before tenant identity is known.

Recommended logical locations:

  • backend/Kds.Ai.Integrations/KdsCdc/Quarantine/
  • or backend/Kds.Ai.Warehouse/Services/Storage/ only if storage behavior is intentionally shared

Recommended logical types:

  • IKdsCdcQuarantineWriter
  • KdsCdcQuarantineWriter
  • KdsCdcQuarantineRecord
  • KdsCdcQuarantineReason

Why new seam is needed:

  • current IFailedPackageWriter assumes CompanyId and TenantId already exist,
  • Task 4 failures occur before tenant-safe S3 path creation,
  • unresolved records still need durable diagnostics and replay reference.

Task 4 should explicitly choose one of these directions:

  1. dedicated unresolved quarantine storage path not requiring tenant id,
  2. generalized failed-package writer expanded to support pre-tenant failures.

Preferred direction:

  • use dedicated KDS CDC quarantine seam for unresolved records instead of widening stage-scoped failed-package behavior too early.

Files to Modify

4. Integration wiring from worker intake

Modify likely KDS CDC intake types from Task 2 so accepted messages flow through tenant resolution before any downstream raw handoff.

Expected areas:

  • backend/Kds.Ai.ApiService/Jobs/KdsCdc/ worker or pump types introduced by Task 2
  • backend/Kds.Ai.Integrations/KdsCdc/ intake models and orchestration seams

Required flow change:

  • parse and validate message,
  • resolve company and tenant,
  • quarantine mapping failures,
  • only resolved records proceed into tenant-scoped batch shaping for Task 5.

5. Data model and repository registration

Modify:

  • backend/Kds.Ai.Data/KdsAiDbContext.cs
  • backend/Kds.Ai.Data/DependencyInjectionExtensions.cs
  • any new EF configuration and migration files needed for source-tenant mapping persistence

Task 4 should define whether mapping storage lives in kdsaidb as first-party configuration metadata. Current repo patterns suggest yes.

6. Warehouse resolution composition

Review and likely modify:

  • backend/Kds.Ai.Warehouse/Services/MetadataTenantWarehouseResolver.cs
  • backend/Kds.Ai.Warehouse/Abstractions/ITenantWarehouseResolver.cs
  • backend/Kds.Ai.Data/Repositories/ITenantWarehouseMappingRepository.cs

Important boundary:

  • do not overload ITenantWarehouseResolver with source-account lookup semantics,
  • keep source-account/store resolution as upstream seam,
  • reuse existing tenant-to-database resolution only after tenant_id is already resolved.

7. Dependency injection and configuration

Modify likely:

  • backend/Kds.Ai.Integrations/DependencyInjectionExtensions.cs
  • backend/Kds.Ai.ApiService/Program.cs only if resolution/quarantine services are host-registered there
  • backend/Kds.Ai.AppHost/AppHostExtensions.cs
  • backend/Kds.Ai.AppHost/AppHost.cs

Configuration may need to cover:

  • enablement of KDS CDC mapping repository/service,
  • local seed or default mapping data for mock CDC flows,
  • unresolved-record quarantine bucket/prefix if separate from stage failed packages.

Task 4 must include a dev/local seed strategy for the mock contract path:

  • seed or configure source_account_id + store_guid -> company_id + tenant_id,
  • align seeded source_account_id with Task 3 simulator producer defaults,
  • keep seed data explicit so tests do not infer tenant from ambient simulator request tenancy.

8. Tests

Add or modify tests under:

  • backend/tests/Kds.Ai.Integrations.Tests/KdsCdc/
  • backend/tests/Kds.Ai.Data.Tests/
  • backend/tests/Kds.Ai.ApiService.Tests/ if host/composition behavior is verified there

Required Behavior

Resolution must happen before tenant-scoped artifacts

Task 4 must enforce feature-doc rule that tenant resolution happens before:

  • tenant-scoped raw S3 path creation,
  • tenant Redshift database resolution for load,
  • ingestion batch creation that requires tenant identity,
  • failed-package writes that assume tenant-scoped storage.

Unresolved records must stop before Task 5 raw persistence path.

Explicit mapping rules

Feature doc mapping rule is fixed:

text
source customer/account -> company_id
source store_guid -> tenant_id within company_id
company_id + tenant_id -> tenant Redshift database

Task 4 must keep these as explicit mappings, not inferred equivalence rules.

Important consequences:

  • source field names do not need to literally be company_id or tenant_id,
  • mapping can come from table or service,
  • mapping result must produce platform company_id and tenant_id values used by later S3 and Redshift paths.

No implicit or ambiguous inference

Task 4 must explicitly reject:

  • missing source account mapping,
  • missing store_guid mapping within known company,
  • multiple tenants matching same source identity,
  • best-effort fallback to default tenant,
  • deriving tenant from simulator defaults or ambient request tenancy,
  • deriving company from customer display name or other fuzzy field.

If mapping is missing or ambiguous, record is quarantined and not loaded.

Resolved tenant context shape

A successful resolution result must carry enough identity for later tasks to proceed without another ambiguous lookup round.

Minimum resolved context:

  • CompanyId
  • TenantId
  • TenantDatabase
  • source account/customer id
  • source StoreGuid
  • mapping evidence identifier or record details useful for diagnostics

Task 4 should freeze whether inactive tenant warehouse mapping is treated as:

  1. mapping failure quarantine, or
  2. downstream batch failure.

Recommended direction:

  • treat missing/ambiguous source mapping as record-local quarantine,
  • treat resolved tenant with missing/inactive warehouse mapping as blocking failure for that resolved tenant slice because tenant identity is known but runtime destination is unusable.

Quarantine metadata requirements

Mapping-failure quarantine must include enough data for diagnosis and replay, matching feature doc intent.

Required metadata:

  • source customer/account value
  • source store_guid
  • source table/entity
  • source primary key
  • Debezium source coordinates
  • SQS message id
  • SQS FIFO group id and ordering value
  • raw payload or raw payload pointer when available
  • reason mapping failed
  • whether failure was missing mapping or ambiguous mapping

If Task 4 occurs before raw S3 write, quarantine record should preserve original message body or safe pointer so mapping can be repaired and replayed later.

Record-local vs batch-level boundary

Task 4 must preserve main feature distinction:

  • missing or ambiguous source mapping is record-local quarantine,
  • good records in same poll window continue,
  • unresolved records never force all good records to fail,
  • later batch-level failures still use stage lifecycle plus failed-package flow once tenant identity exists.

Task 4 should clearly separate:

  1. pre-tenant quarantine for bad records,
  2. tenant-scoped stage failures after resolution succeeds.

No unresolved writes to tenant paths

Task 4 must guarantee unresolved records never reach:

  • raw/company_id=.../tenant_id=...
  • load/company_id=.../tenant_id=...
  • curated/company_id=.../tenant_id=...
  • tenant warehouse databases in Redshift
  • stage-batch creation calls requiring TenantId and TenantDatabase

This is core safety boundary for multi-tenant correctness.

Fit with Task 2 and Task 5

Task 4 should preserve this handoff model:

  1. Task 2 intake validates message transport/body/order shape,
  2. Task 4 resolves tenant identity and quarantines mapping failures,
  3. Task 5 persists only resolved records into raw S3 and durable batch state.

Task 4 must not collapse these responsibilities into one opaque worker step.

Proposed Implementation Shape

Recommended high-level flow:

text
KdsCdc worker intake
  -> parsed/validated CDC message
  -> IKdsCdcTenantResolver.ResolveAsync(...)
      -> success: resolved company_id + tenant_id + tenant database
      -> missing/ambiguous mapping: quarantine record with full source diagnostics
  -> only resolved records continue to tenant/entity micro-batch handoff for Task 5

Recommended resolver contract shape:

csharp
public interface IKdsCdcTenantResolver
{
    Task<KdsCdcTenantResolutionResult> ResolveAsync(
        KdsCdcTenantResolutionRequest request,
        CancellationToken ct = default);
}

Recommended resolution request shape:

text
KdsCdcTenantResolutionRequest
  SourceAccountId
  StoreGuid
  Entity
  PrimaryKey
  MessageId
  MessageGroupId
  SequenceValue
  SourceCoordinates

Recommended resolution result shape:

text
KdsCdcTenantResolutionResult
  Outcome: Resolved | MissingMapping | AmbiguousMapping | InactiveTenantWarehouse
  CompanyId
  TenantId
  TenantDatabase
  Diagnostics

Recommended quarantine writer shape:

csharp
public interface IKdsCdcQuarantineWriter
{
    Task WriteAsync(KdsCdcQuarantineRecord record, CancellationToken ct = default);
}

Important design notes:

  • keep source mapping lookup separate from warehouse lookup,
  • keep resolver pure enough for unit testing,
  • do not require CreateIngestionBatchRequest before resolution succeeds,
  • do not force unresolved records through StageExecutionSupport.FailStageBatchAsync, because no tenant-scoped stage batch should exist yet.

Testing

Unit tests

Add tests for:

  • resolver maps valid source_account_id + store_guid to expected company_id, tenant_id, and tenant database,
  • missing source account mapping returns quarantine outcome,
  • missing store mapping within known company returns quarantine outcome,
  • ambiguous mapping returns quarantine outcome,
  • resolver does not fall back to default tenant or ambient tenant context,
  • inactive or missing tenant warehouse mapping produces documented outcome,
  • quarantine record includes required source diagnostics and reason,
  • unresolved record cannot produce tenant-scoped raw/load key inputs,
  • resolved record carries enough identity for later batch creation.

Repository/data tests

Add tests for:

  • mapping repository stores and retrieves source account plus store_guid mappings deterministically,
  • uniqueness rules prevent ambiguous active mappings for same source identity,
  • disabled/inactive mappings are handled per chosen resolution policy.

Integration/composition tests

Add tests proving:

  • Task 2 worker-facing composition routes unresolved records to quarantine writer,
  • good and bad records in same intake window split correctly,
  • unresolved records do not call tenant-scoped stage-batch creation or tenant-scoped S3 writers,
  • resolved records continue toward downstream handoff,
  • current stage-scoped failed-package path remains reserved for post-resolution stage failures.

Not required in task 4

This task does not require:

  • raw S3 persistence tests,
  • durable job creation tests,
  • Redshift load tests,
  • curated merge/export tests,
  • replay orchestration implementation,
  • UI/admin workflows for maintaining mappings unless later user request adds them.

Acceptance Criteria

  • [ ] KDS CDC has explicit source-account/store to company/tenant resolution seam
  • [ ] Mapping rule follows source customer/account -> company_id and store_guid -> tenant_id within company_id
  • [ ] No implicit tenant inference or best-effort fallback is allowed
  • [ ] Missing mappings are quarantined with full source diagnostics
  • [ ] Ambiguous mappings are quarantined with full source diagnostics
  • [ ] Unresolved records never create tenant-scoped S3 paths or Redshift loads
  • [ ] Resolver produces tenant database identity for resolved records, directly or via existing warehouse resolver seam
  • [ ] Pre-tenant quarantine seam exists and is not incorrectly forced through current tenant-scoped failed-package writer
  • [ ] Good records can continue even when bad records in same intake window are quarantined
  • [ ] Local/dev mapping seed or configuration supports mock producer source_account_id + store_guid values explicitly
  • [ ] Tests cover valid resolution, missing mapping, ambiguous mapping, and unresolved-write blocking behavior

Notes

  • Follow docs/features/ETL/kds-cdc/index.md#stage-3-tenant-resolution as source of truth.
  • Current ITenantWarehouseResolver is downstream resolver for known tenant id; it is not sufficient by itself for CDC source-account/store mapping.
  • Current IFailedPackageWriter is tenant-scoped and batch-scoped; unresolved CDC mapping failures need a separate pre-tenant quarantine path unless later work intentionally generalizes storage contracts.
  • Keep Task 4 focused on identity resolution and quarantine boundary. Raw persistence starts in Task 5.