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)

Brink & Borg Architecture (2)

Brink POS ingestion path analysis, current state, gaps, and The Borg unified ingestion architecture.
brink-and-borg/analysis.md 12,589 bytes · Updated Oct 26, 2026 10:38 AM EDT
Download .docx Raw File

Spensara (KDSAI) — Brink POS ingestion path: current state, gaps, recommended build

Prepared 2026-10-23 (ET). Read-only analysis. No commits, pushes, or AWS changes.

0. What I could and could not read (read this first)

SourceResult
GitHub (primary)Not reachable. No Origin namespaces visible; gh on the box is not logged in.
Xstore Gitea mirror (192.168.1.247:3000)Reachable from TomDevAI, but an anonymous read shows 0 repos. Repo Bematechus/KDSAI, branch main, needs a token. I did not use the vault GITEA_XSTORE_TOKEN, because pulling a credential to give myself access isn't sanctioned without Tom's go-ahead.
Box: /workspace/research/kg/trees/tree_KDSAI.jsonA file listing of KDSAI main from an earlier read-only research pass (Oct 8). It has 1,000 of 2,091 paths, so it is truncated. It has file names only, no file contents.
Y:\xampp\htdocs\Spensara\SESSION_LOG.md (TomHO11)Sage's notes from cloning and running KDSAI on spensara-sandbox (commit f2962c7). These were the main source for runtime behavior.
Memory / deliverablesTom plans to take over the Redshift load from the Poland team. Maciej Kryca has an open "Redshift-to-S3 migration PR, then Athena for multi-tenant reads."
AWSNo CLI or credentials on the box. I verified no live resources: no Lambda, Glue, EventBridge, Redshift Serverless workgroups, or S3 buckets.

Everything below about how the code behaves comes from file and class names plus the session log. Each point is labelled [seen] (a path exists or the log states it) or [inferred]. To confirm the inferred points, someone with Gitea access needs to read the roughly 12 files listed in §5.

1. End-to-end map (current)

Everything runs in one .NET 10 / Aspire app (backend/Kds.Ai.slnx, orchestrated by backend/Kds.Ai.AppHost/AppHost.cs). There are no Lambda, Glue or Step Functions in the tree for Brink [seen: no such paths in KDSAI]. A separate repo, KDS.AWS.Devops.Infrastructure, holds the infrastructure code.

Brink POS API (per-location token + service URL)
  │  BrinkPosEndpointCatalog / BrinkPosOperationCatalog / BrinkPosEndpointExecutor
  ▼
[1 Extract]  BrinkPosExtractOrchestrator → BrinkPosExtractStage / BrinkPosExtractJobs
  │  business-date windows: BrinkPosBusinessDateResolver, BrinkPosScheduleDateWindowBuilder,
  │  BrinkPosScheduleTimeZoneResolver, BrinkPosScheduleDefaults
  │  → raw payloads to S3 (EtlS3Options:BucketName; Services/Storage/AmazonS3ObjectStoreClient.cs)   [inferred raw zone]
  ▼
[2 Load→Staging]  BrinkPosLoadToStagingStage/Jobs, BrinkPosStagingTableCatalog
  │  → Redshift tenant DB, schema `staging` (37 tables; e.g. 0619_create_staging_brink_settings_get_modifier_groups.sql uses SUPER) [seen]
  ▼
[3 Normalize]  BrinkPosNormalizeLoadStage/Jobs, BrinkPosNormalizationCatalog, BrinkPosNormalizationContextFactory
  ▼
[4 Staging→Curated]  BrinkPosStagingToCuratedStage/Jobs, BrinkPosCuratedTargetCatalog, Etl/CuratedTargetExecutor.cs
  │  → schema `curated` (11 tables) [seen]
  ▼
[5 Curated→S3 export]  BrinkPosCuratedToS3Stage/Jobs, BrinkPosCuratedExportCatalog, Etl/CuratedExportExecutor.cs
  │  (Parquet curated zone; this is the Athena/multi-tenant-read direction in Maciej's PR) [inferred]
  ▼
Spensara API / analysis layer
   ApiService/Services/RedshiftWarehouseOrderReadService.cs, Endpoints/WarehouseOrderEndpoints.cs
   AnomalyDetector/Tools/WarehouseQueryToolProvider.cs, Agents/DataAccessAgentFactory.cs (read-only, tenant-scoped, 500 rows / 30 s)

Shared framework (backend/Kds.Ai.Integrations/Etl/) includes EtlPipelineRunner, IEtlStage, IEtlSourceAdapter, StageExecutionSupport, StageCheckpointAdvanceRequest and StageBatchFailureRequest. State is kept in the operational Postgres through Data/Entities/IngestionBatch.cs, IngestionCheckpoint.cs, Enums/IngestionMode.cs and IngestionBatchStatus.cs, added by migration 20260508093108_AddEtlIngestionTables. Credentials come from Data/Credentials/BrinkPosCredentialPayload.cs and CustomerCredentialBackedBrinkCredentialReadService.cs, falling back to BrinkConnectionOptionsFallbackResolver.cs (the AppHost parameters brink-pos-access-token, -location-token and -service-url). Per-tenant provisioning is handled by Services/CustomerEtlProvisioningService.cs, Jobs/CustomerWarehouseProvisioningJobs.cs and Data/Entities/TenantWarehouseMapping.cs.

The KDS side has a more mature sibling pipeline: Debezium → SQS FIFO → KdsCdc*, with a duplicate detector, quarantine records, a replay index and a promotion retry service. The Brink pipeline has none of these (no quarantine or replay files exist under BrinkPos).

Warehouse status: Postgres RDS (kds-ai-postgres…us-west-2) is still in use, and Redshift Serverless is a migration in progress (per docs/REDSHIFT.md, quoted in the session log).

2. Manual steps and their triggers today

#StepTrigger today
M1Tenant warehouse schema creation (SetupTenantWarehouse, Aspire .WithExplicitStart())A person clicks Start in the Aspire dashboard or runs the CLI (Kds.Ai.Cli Commands/Warehouse/WarehouseCommand.cs) [seen: "deliberately manual, by design"]
M2Tenant mapping (ensure-tenant-warehouse)CLI, by a person [seen]
M3Brink credentials and location bindingEntered by an operator into the customer credential store, or AppHost user-secrets [seen]
M4Running Extract → Staging → Normalize → Curated → S3An HTTP call to Endpoints/BrinkPosEtlEndpoints.cs (or CLI) by a developer. Models/BrinkPosEtlTriggers.cs / BrinkPosExtractTrigger.cs suggest a "manual" vs "scheduled" trigger enum [inferred].
M5SchedulingJobs/BrinkCronSyncService.cs exists (a hosted cron service) [seen]. Tom reports there is no schedule in practice, so it is most likely disabled by config, not deployed, or covering extract only [inferred; must confirm].
M6Redshift-only SQL fixes (SUPER vs JSONB) and the CLI launch-profile typo (--tenantId)Fixed by hand when they come up [seen]
M7Re-running a failed dayManual re-invocation; there is no replay or quarantine tooling for Brink [inferred]

3. Gaps blocking automatic near-real-time landing in curated

  1. The trigger is in-process. Any cron runs inside the ApiService pod. If the API scales out, it fires twice; if the API is down, nothing runs. There's no external scheduler and no lease or lock [inferred].
  2. Incremental / watermark handling. IngestionCheckpoint exists, but the Brink window builder works in business-date windows. Brink's documented model is SOAP/REST pulls by business date (for example GetOrders per location per day), not a change feed. Near-real-time therefore means re-pulling "today" every N minutes, which needs an intraday watermark (last order modified time / order id) [inferred; Brink has no webhooks/stream to my knowledge — assumption].
  3. Idempotency / dedupe. Re-pulling today returns the same orders again. Staging → curated must be a key-based MERGE (Redshift supports MERGE, not ON CONFLICT). The Brink path has no duplicate detector like KdsCdcDuplicateDetector [seen absent].
  4. Schema drift. Raw lands in SUPER (good), but BrinkPosNormalizationCatalog is hard-coded contracts. There's no drift detection, quarantine (KdsCdcQuarantineRecord has no Brink twin), or alert when a new field or type appears.
  5. Retries / backoff. No visible Polly or retry policy for Brink rate limits and timeouts, and no dead-letter. StageBatchFailureRequest records failure but doesn't retry [inferred].
  6. Secrets. Three plaintext credentials are committed (appsettings.Development.json, Cli/Properties/launchSettings.json to real RDS as the postgres superuser). The Brink tokens fall back to app parameters. They should move to Secrets Manager per tenant and the exposed ones should be rotated [seen].
  7. Monitoring. No CloudWatch metrics or alarms on freshness, rows per run or failures. Batch status sits only in Postgres tables.
  8. Warehouse target is ambiguous. RDS vs Redshift Serverless vs (Maciej's PR) S3+Athena. The automation has to target one source of truth for "curated", or it will be rebuilt twice.
  9. Provisioning is manual (M1/M2). A new customer can't go live without a person.

Principle: keep the existing .NET stages (they're already modular via IEtlStage). Replace the trigger and state machine, and harden each stage. Don't rewrite in Glue.

#ComponentDetailEffort
B1Stage runner as an ECS Fargate task (or a worker container)Package Kds.Ai.Cli, or a new Kds.Ai.EtlWorker, with a run-brink --tenant --location --stage --window command. Same code, out of the API process.3–4 d
B2EventBridge Scheduler → Step Functions (one state machine per tenant, using a Map over locations)Extract → LoadToStaging → Normalize → StagingToCurated → (optional) CuratedToS3. Each step is an ECS RunTask (.sync) with retry (3×, exponential backoff, jitter) and Catch → failure state → SNS. One execution name per tenant/window gives idempotent start, so no double-fire.4–5 d
B3CadenceIntraday: every 5 min for the current business date (target: curated ≤ 10 min behind the POS; the Brink rate limit may force 15 min, to be confirmed with PAR). Nightly 04:00 local: a full re-pull of D-1 and D-2 to catch voids, refunds and late closes. Weekly: a D-7 reconcile.incl.
B4WatermarksExtend IngestionCheckpoint to (tenant, location, endpoint, business_date, last_modified_utc / max_order_id). Advance only after the curated MERGE commits.2 d
B5Idempotent curated loadStaging gets a batch_id, a _ingested_at column and a natural key. Curated uses MERGE INTO curated.x USING (dedup with ROW_NUMBER) … inside one transaction per batch. Delete-then-insert per (location, business_date) for small dimension tables.3–4 d
B6Drift and quarantineRaw JSON stays in S3 + SUPER. The normalizer validates against a versioned contract, and unknown or invalid records go to brink_quarantine (mirror KdsCdcQuarantineRecord). New fields alert and stay in raw; breaking changes stop that endpoint only.3 d
B7SecretsPer-tenant Brink tokens in Secrets Manager (spensara/{tenant}/brink), read by the task role. Rotate the three exposed DB credentials and remove them from git history.1–2 d
B8Monitoring / alertingEmit CloudWatch metrics (rows, latency, freshness lag = now − max curated order time) for each location. Alarms: lag > 30 min intraday, any Step Functions failure, quarantine > 0. SNS → email/Teams. A freshness view in Bridge Admin is optional.2–3 d
B9Provisioning automationWhen a customer is created, run SetupTenantWarehouse + mapping + schedule creation as a Step Functions branch (removes M1–M3).2–3 d
B10Decide the warehousePick Redshift Serverless (with MERGE and SUPER) as curated, with S3 Parquet export kept for Athena and replay. Fix the SUPER/JSONB dev split with a dialect switch. Merge Maciej's PR to match.1 d (decision + review)
B11Tests and runbookUse the simulator plus a recorded-Brink-payload replay test, and a backfill command (--from --to).3 d

Total ≈ 27–33 engineer-days (about 6 weeks for one engineer, or 3–4 weeks for two). The smallest useful slice is B1+B2+B5+B7 with a 15-minute cadence, about 2.5 weeks.

Streaming (Kinesis / Redshift streaming ingestion) doesn't fit unless PAR offers a push feed. Brink is pull-based, so scheduled micro-batches are the right model [assumption].

5. To confirm with Gitea or GitHub read access (about 1 hour)

Jobs/BrinkCronSyncService.cs (is it enabled, and what's the cron?), Endpoints/BrinkPosEtlEndpoints.cs, Models/BrinkPosEtlTriggers.cs, BrinkPosScheduleDefaults.cs, BrinkPosStagingToCuratedStage.cs (insert vs merge), Etl/CuratedTargetExecutor.cs, BrinkPosExtractStage.cs (does raw go to S3 first?), AppHost.cs, .github/workflows/backend-pipeline.yml, docs/REDSHIFT.md, and KDS.AWS.Devops.Infrastructure (is there an existing ECS/EventBridge/Redshift definition?). Also needed: read-only AWS access in us-west-2 to list Redshift Serverless workgroups, S3 buckets, ECS services and EventBridge schedules.