diff --git a/docs/audit/tracker-gap-reference-catalog.md b/docs/audit/tracker-gap-reference-catalog.md
index a7de74ab..678d8a66 100644
--- a/docs/audit/tracker-gap-reference-catalog.md
+++ b/docs/audit/tracker-gap-reference-catalog.md
@@ -145,7 +145,7 @@ This catalog explains each gap: problem, purpose, evidence, closure criteria, an
- **Acceptance criteria:**
- [ ] A satellite can hold a multi-dimensional topology set validated against Core's composition rules.
- **Dependencies:** [CR-09](#cr-09), [CR-10](#cr-10), [CR-19](#cr-19); ADR-0079.
- - **Status:** `IN-PROGRESS` — topología multi-dimensional en frontend + catálogo BFF estático; el dominio aún usa un único string `Topology`, falta el agregado compuesto/validación/persistencia/sync.
+ - **Status:** `DONE` — `TopologyCatalog` + VO `TopologySet` (validación dura + avisos de composición), prop en `Product` + `SetProductTopologySet`, persistencia jsonb + migración `AddProductTopologySet`, endpoint `topology-set`; 473 tests. Diferido: sync con Core (GAP-004) y unificar el catálogo estático de Presentation.
#### CR-09
diff --git a/docs/audit/tracker-gap-tracking.md b/docs/audit/tracker-gap-tracking.md
index 0d20f7f8..5cecd21a 100644
--- a/docs/audit/tracker-gap-tracking.md
+++ b/docs/audit/tracker-gap-tracking.md
@@ -77,7 +77,7 @@ This board is the single source of truth for Tracker technical debt, gaps, oppor
| [`AR-20`](./tracker-gap-reference-catalog.md#ar-20) | Sin structured logging ni correlation IDs wiring completo | `Infra` | Cross | P3 | M | `PENDING` |
| [`AR-21`](./tracker-gap-reference-catalog.md#ar-21) | Sin rate limiting ni security headers configurados | `Security` | Cross | P3 | S | `PENDING` |
| [`CR-03`](./tracker-gap-reference-catalog.md#cr-03) | "100% mock" es falso: `CoreEvaluationGateway` (typed client → `POST evaluate`, envelope ADR-0073, ACL) está cableado en el flujo de phase-gate y persiste `CoreEvaluationTransaction`. Falta: verificación e2e contra un Core real (**GAP-004**). | `Integration/WEB` | Cross | P0 | M | `IN-PROGRESS` |
-| [`CR-08`](./tracker-gap-reference-catalog.md#cr-08) | Topología multi-dimensional/componible existe en frontend + catálogo BFF estático, pero el agregado de dominio sigue con un único string `Topology` (`ProductProps`); faltan agregado de conjunto compuesto, validación de composición en dominio, persistencia y sync con Core. | `Arch/Backend` | Cross | P0 | M | `IN-PROGRESS` |
+| [`CR-08`](./tracker-gap-reference-catalog.md#cr-08) | **Hecho:** modelo de topología componible en el dominio — `TopologyCatalog` autoritativo (5 dimensiones + reglas `composableWith`), VO `TopologySet` con validación dura (dimensiones requeridas + topología válida por dimensión) y avisos de composición no bloqueantes, prop en `Product` + `SetProductTopologySet` (deriva el `Topology` legacy), persistencia jsonb `topology_set` + migración aditiva `AddProductTopologySet`, endpoint `POST /{id}/topology-set`. 473 tests (+15). **Diferido:** sync con Core (depende de `GAP-004`); unificar el catálogo estático de Presentation con `TopologyCatalog` (follow-up menor). | `Arch/Backend` | Cross | P0 | M | `DONE` |
| [`CR-13`](./tracker-gap-reference-catalog.md#cr-13) | **Hecho:** VO de dominio `TechnicalEvaluationResult` (outcome compliant/non_compliant/error + `FromCoreVerdict` cableado al `CoreVerdict` real); `GateDecisionStatus` canónico (pending/approved/rejected/approved_with_exception) validado en `Decide()` con la regla `approved_with_exception` ⇒ requiere ≥1 exceptionId; refs `policySnapshotRef`/`evidenceSnapshotRef`/`approvalIds`/`exceptionIds` en el agregado + migración aditiva `AddGateDecisionEvaluationRefs`. Build 0-warnings, 443 tests (+15). | `Backend/WEB` | Cross | P1 | M | `DONE` |
| [`CR-24`](./tracker-gap-reference-catalog.md#cr-24) | Specs de diseño cubren evaluación satélite, provider ports/ACL y evidencia; pero las **user stories** de capacidad enumeradas (incl. US-ARC-001/006 que el estado citaba) **no existen** en `reference/specs/` — autorar esas historias o replantear el ítem. | `Specs` | Cross | P1 | M | `IN-PROGRESS` |
| [`CR-28`](./tracker-gap-reference-catalog.md#cr-28) | README phase model drift (5 vs 6 gates) + canonical PhaseId vocabulary not adopted | `Docs` | Cross | P2 | S | `IN-PROGRESS` |
diff --git a/src/apps/tracker-api/Tracker.Application/Products/Product/Commands/SetProductTopologySet/SetProductTopologySetCommand.cs b/src/apps/tracker-api/Tracker.Application/Products/Product/Commands/SetProductTopologySet/SetProductTopologySetCommand.cs
new file mode 100644
index 00000000..534e9e96
--- /dev/null
+++ b/src/apps/tracker-api/Tracker.Application/Products/Product/Commands/SetProductTopologySet/SetProductTopologySetCommand.cs
@@ -0,0 +1,14 @@
+using Tracker.Application.Abstractions.Messaging;
+using Tracker.Application.Products.Product.DTOs;
+
+namespace Tracker.Application.Products.Product.Commands.SetProductTopologySet;
+
+///
+/// CR-08 (ADR-0079). Sets the composed multi-dimensional topology of a Product.
+/// maps each dimension key to a topology id.
+///
+public sealed record SetProductTopologySetCommand(
+ Guid TenantId,
+ Guid ProductId,
+ IReadOnlyDictionary Assignments
+) : ICommand;
diff --git a/src/apps/tracker-api/Tracker.Application/Products/Product/Commands/SetProductTopologySet/SetProductTopologySetCommandHandler.cs b/src/apps/tracker-api/Tracker.Application/Products/Product/Commands/SetProductTopologySet/SetProductTopologySetCommandHandler.cs
new file mode 100644
index 00000000..61d14939
--- /dev/null
+++ b/src/apps/tracker-api/Tracker.Application/Products/Product/Commands/SetProductTopologySet/SetProductTopologySetCommandHandler.cs
@@ -0,0 +1,47 @@
+using Tracker.Application.Abstractions.Messaging;
+using Tracker.Application.Products.Product.DTOs;
+using Tracker.Domain.Kernel;
+using Tracker.Domain.Products.Product.Topology;
+
+namespace Tracker.Application.Products.Product.Commands.SetProductTopologySet;
+
+internal sealed class SetProductTopologySetCommandHandler
+ : ICommandHandler
+{
+ private readonly Tracker.Domain.Products.Product.IProductRepository _repository;
+ private readonly IUnitOfWork _unitOfWork;
+
+ public SetProductTopologySetCommandHandler(
+ Tracker.Domain.Products.Product.IProductRepository repository,
+ IUnitOfWork unitOfWork)
+ {
+ _repository = repository;
+ _unitOfWork = unitOfWork;
+ }
+
+ public async Task> Handle(
+ SetProductTopologySetCommand request,
+ CancellationToken cancellationToken)
+ {
+ var product = await _repository.GetByIdAsync(request.ProductId, cancellationToken);
+
+ if (product is null)
+ return Result.Failure("Product.NotFound");
+
+ if (product.TenantId != request.TenantId)
+ return Result.Failure("Product.AccessDenied");
+
+ var setResult = TopologySet.Create(request.Assignments);
+ if (setResult.IsFailure)
+ return Result.Failure(setResult.Error);
+
+ var result = product.SetTopologySet(setResult.Value);
+ if (result.IsFailure)
+ return Result.Failure(result.Error);
+
+ await _repository.UpdateAsync(product, cancellationToken);
+ await _unitOfWork.SaveEntitiesAsync(cancellationToken);
+
+ return Result.Success(ProductDtoMapper.ToDto(product));
+ }
+}
diff --git a/src/apps/tracker-api/Tracker.Domain/Products/Product/Product.cs b/src/apps/tracker-api/Tracker.Domain/Products/Product/Product.cs
index a7e07080..4714c997 100644
--- a/src/apps/tracker-api/Tracker.Domain/Products/Product/Product.cs
+++ b/src/apps/tracker-api/Tracker.Domain/Products/Product/Product.cs
@@ -1,4 +1,5 @@
using Tracker.Domain.Kernel;
+using Tracker.Domain.Products.Product.Topology;
namespace Tracker.Domain.Products.Product;
@@ -13,6 +14,7 @@ private Product(ProductProps props) : base(props) { }
public string Description => Props.Description;
public string Status => Props.Status;
public string? Topology => Props.Topology;
+ public TopologySet? TopologySet => Props.TopologySet;
public string? RepositoryUrl => Props.RepositoryUrl;
public string? OwnerIdentity => Props.OwnerIdentity;
public Dictionary Metadata => Props.Metadata;
@@ -34,6 +36,7 @@ public static Result Create(
Description = description,
Status = "active",
Topology = null,
+ TopologySet = null,
RepositoryUrl = null,
OwnerIdentity = null,
Metadata = new(),
@@ -96,6 +99,27 @@ public Result SetTopology(string topology)
return Result.Success();
}
+ ///
+ /// CR-08 (ADR-0079). Stores the composed multi-dimensional and derives
+ /// the legacy scalar from its progressive-axis for back-compat. The set is
+ /// assumed already validated via .
+ ///
+ public Result SetTopologySet(TopologySet set)
+ {
+ if (set is null)
+ return Result.Failure("Product.TopologySetRequired");
+
+ SetProps(Props with
+ {
+ TopologySet = set,
+ Topology = set.ProgressiveAxis
+ });
+ TrackingState.MarkAsDirty();
+
+ // TODO CR-08: sync with Core
+ return Result.Success();
+ }
+
public Result Archive()
{
if (Props.Status == "archived")
diff --git a/src/apps/tracker-api/Tracker.Domain/Products/Product/ProductProps.cs b/src/apps/tracker-api/Tracker.Domain/Products/Product/ProductProps.cs
index a52b9d40..c0660f74 100644
--- a/src/apps/tracker-api/Tracker.Domain/Products/Product/ProductProps.cs
+++ b/src/apps/tracker-api/Tracker.Domain/Products/Product/ProductProps.cs
@@ -1,4 +1,5 @@
using Tracker.Domain.Kernel;
+using Tracker.Domain.Products.Product.Topology;
namespace Tracker.Domain.Products.Product;
@@ -11,7 +12,12 @@ public sealed record ProductProps : IProps
public string Name { get; init; } = string.Empty;
public string Description { get; init; } = string.Empty;
public string Status { get; init; } = string.Empty;
+
+ /// Legacy scalar topology (kept for back-compat; derived from the progressive-axis of ).
public string? Topology { get; init; }
+
+ /// CR-08 (ADR-0079). Composed multi-dimensional topology. Nullable until set.
+ public TopologySet? TopologySet { get; init; }
public string? RepositoryUrl { get; init; }
public string? OwnerIdentity { get; init; }
public Dictionary Metadata { get; init; } = new();
diff --git a/src/apps/tracker-api/Tracker.Domain/Products/Product/Topology/TopologyCatalog.cs b/src/apps/tracker-api/Tracker.Domain/Products/Product/Topology/TopologyCatalog.cs
new file mode 100644
index 00000000..55aa29ae
--- /dev/null
+++ b/src/apps/tracker-api/Tracker.Domain/Products/Product/Topology/TopologyCatalog.cs
@@ -0,0 +1,89 @@
+namespace Tracker.Domain.Products.Product.Topology;
+
+///
+/// CR-08 (ADR-0079). Authoritative, Tracker-owned catalog of the composable
+/// multi-dimensional architecture topology. Encoded VERBATIM from the canonical model
+/// shared with the frontend and the static BFF catalog:
+/// five dimensions (four required, one optional) plus a lenient composableWith map
+/// used only for ADVISORY composition warnings — never for hard validation.
+/// Uses static readonly collections so there are no uninitialized fields (no CS8618).
+///
+public static class TopologyCatalog
+{
+ /// Canonical dimension keys in authoritative order.
+ public const string ProgressiveAxisDimension = "progressive-axis";
+ public const string ExecutionDimension = "execution";
+ public const string IntegrationDimension = "integration";
+ public const string DataDimension = "data";
+ public const string AiDimension = "ai";
+
+ private static readonly IReadOnlyList DimensionDefinitions = new List
+ {
+ new(ProgressiveAxisDimension, Required: true, new[] { "modular-monolith", "distributed-modules", "microservices" }),
+ new(ExecutionDimension, Required: true, new[] { "containerized-services", "serverless", "edge-computing" }),
+ new(IntegrationDimension, Required: true, new[] { "api-led", "event-driven" }),
+ new(DataDimension, Required: true, new[] { "shared-database", "data-mesh", "event-sourced" }),
+ new(AiDimension, Required: false, new[] { "human-in-loop", "agentic-ai" })
+ };
+
+ private static readonly IReadOnlyDictionary DimensionsByKey =
+ DimensionDefinitions.ToDictionary(d => d.Key, StringComparer.Ordinal);
+
+ ///
+ /// Advisory composition adjacency (per topology id). NOT hard validation:
+ /// canonical blueprint sets (e.g. event-sourced + agentic-ai together) remain VALID.
+ ///
+ private static readonly IReadOnlyDictionary> ComposableWith =
+ new Dictionary>(StringComparer.Ordinal)
+ {
+ ["modular-monolith"] = new[] { "containerized-services", "api-led", "shared-database", "human-in-loop" },
+ ["distributed-modules"] = new[] { "containerized-services", "event-driven", "api-led", "event-sourced", "human-in-loop" },
+ ["microservices"] = new[] { "containerized-services", "serverless", "event-driven", "data-mesh", "event-sourced", "agentic-ai" },
+ ["containerized-services"] = new[] { "modular-monolith", "distributed-modules", "microservices", "api-led", "event-driven", "shared-database", "data-mesh" },
+ ["serverless"] = new[] { "microservices", "event-driven", "data-mesh", "agentic-ai" },
+ ["edge-computing"] = new[] { "microservices", "event-driven", "agentic-ai" },
+ ["api-led"] = new[] { "modular-monolith", "distributed-modules", "microservices", "containerized-services", "shared-database", "human-in-loop" },
+ ["event-driven"] = new[] { "distributed-modules", "microservices", "serverless", "event-sourced", "data-mesh", "agentic-ai" },
+ ["shared-database"] = new[] { "modular-monolith", "containerized-services", "api-led", "human-in-loop" },
+ ["data-mesh"] = new[] { "microservices", "event-driven", "serverless", "agentic-ai" },
+ ["event-sourced"] = new[] { "distributed-modules", "microservices", "event-driven", "serverless" },
+ ["human-in-loop"] = new[] { "modular-monolith", "api-led", "shared-database", "containerized-services" },
+ ["agentic-ai"] = new[] { "microservices", "event-driven", "serverless", "data-mesh" }
+ };
+
+ /// All canonical dimension keys in authoritative order.
+ public static IReadOnlyList Dimensions { get; } =
+ DimensionDefinitions.Select(d => d.Key).ToList();
+
+ /// True if is a known dimension and is required.
+ public static bool IsRequired(string dimension)
+ => DimensionsByKey.TryGetValue(dimension, out var def) && def.Required;
+
+ /// True if is a known dimension key.
+ public static bool IsKnownDimension(string dimension)
+ => DimensionsByKey.ContainsKey(dimension);
+
+ /// True if is an allowed topology for .
+ public static bool IsValidTopology(string dimension, string topologyId)
+ => DimensionsByKey.TryGetValue(dimension, out var def)
+ && def.AllowedTopologies.Contains(topologyId);
+
+ ///
+ /// Lenient symmetric composition check: true if EITHER topology lists the other in its
+ /// composableWith set. Identical ids are trivially composable. Advisory only.
+ ///
+ public static bool AreComposable(string idA, string idB)
+ {
+ if (string.Equals(idA, idB, StringComparison.Ordinal))
+ return true;
+
+ var aLists = ComposableWith.TryGetValue(idA, out var a) && a.Contains(idB);
+ var bLists = ComposableWith.TryGetValue(idB, out var b) && b.Contains(idA);
+ return aLists || bLists;
+ }
+
+ private sealed record DimensionDefinition(
+ string Key,
+ bool Required,
+ IReadOnlyList AllowedTopologies);
+}
diff --git a/src/apps/tracker-api/Tracker.Domain/Products/Product/Topology/TopologySet.cs b/src/apps/tracker-api/Tracker.Domain/Products/Product/Topology/TopologySet.cs
new file mode 100644
index 00000000..6695314f
--- /dev/null
+++ b/src/apps/tracker-api/Tracker.Domain/Products/Product/Topology/TopologySet.cs
@@ -0,0 +1,120 @@
+using Tracker.Domain.Kernel;
+
+namespace Tracker.Domain.Products.Product.Topology;
+
+///
+/// CR-08 (ADR-0079). Value object for a composed, multi-dimensional architecture topology:
+/// an immutable, ordered map of dimension key -> topology id.
+///
+/// The HARD validation rule mirrors the frontend validateTopologySet EXACTLY: a set is
+/// valid iff (1) every REQUIRED dimension is assigned, (2) every assigned topology id is allowed
+/// for its dimension, and (3) there are no unknown dimension keys. Composability is ADVISORY only
+/// (see ) and never blocks creation, so canonical blueprint sets
+/// (e.g. event-sourced + agentic-ai in the same set) remain VALID.
+///
+///
+public sealed record TopologySet
+{
+ private TopologySet(IReadOnlyDictionary assignments)
+ {
+ Assignments = assignments;
+ }
+
+ /// Immutable, ordered map of dimension key -> topology id.
+ public IReadOnlyDictionary Assignments { get; }
+
+ /// The progressive-axis topology id, if assigned. Used to derive the legacy scalar Topology.
+ public string? ProgressiveAxis => Assignments.GetValueOrDefault(TopologyCatalog.ProgressiveAxisDimension);
+
+ ///
+ /// Builds a validated applying the HARD rule. Assignments are stored
+ /// in the catalog's authoritative dimension order for deterministic persistence.
+ ///
+ public static Result Create(IReadOnlyDictionary assignments)
+ {
+ if (assignments is null)
+ return Result.Failure("TopologySet.AssignmentsRequired");
+
+ // (3) No unknown dimension keys, and (2) each topology id is allowed for its dimension.
+ foreach (var (dimension, topologyId) in assignments)
+ {
+ if (!TopologyCatalog.IsKnownDimension(dimension))
+ return Result.Failure("TopologySet.UnknownDimension");
+
+ if (!TopologyCatalog.IsValidTopology(dimension, topologyId))
+ return Result.Failure("TopologySet.InvalidTopologyForDimension");
+ }
+
+ // (1) Every required dimension must be assigned.
+ foreach (var dimension in TopologyCatalog.Dimensions)
+ {
+ if (TopologyCatalog.IsRequired(dimension) && !assignments.ContainsKey(dimension))
+ return Result.Failure("TopologySet.MissingRequiredDimension");
+ }
+
+ // Store in authoritative dimension order for deterministic jsonb round-trips.
+ var ordered = new Dictionary(StringComparer.Ordinal);
+ foreach (var dimension in TopologyCatalog.Dimensions)
+ {
+ if (assignments.TryGetValue(dimension, out var topologyId))
+ ordered[dimension] = topologyId;
+ }
+
+ return Result.Success(new TopologySet(ordered));
+ }
+
+ ///
+ /// ADVISORY, non-blocking composition warnings: distinct cross-dimension topology pairs whose
+ /// ids are NOT . Each pair is reported once.
+ ///
+ public IReadOnlyList<(string, string)> CompositionWarnings()
+ {
+ var ids = Assignments.Values.ToList();
+ var warnings = new List<(string, string)>();
+
+ for (var i = 0; i < ids.Count; i++)
+ {
+ for (var j = i + 1; j < ids.Count; j++)
+ {
+ if (!TopologyCatalog.AreComposable(ids[i], ids[j]))
+ warnings.Add((ids[i], ids[j]));
+ }
+ }
+
+ return warnings;
+ }
+
+ /// Value equality over the ordered assignment pairs (records compare the dictionary by reference).
+ public bool Equals(TopologySet? other)
+ {
+ if (other is null)
+ return false;
+ if (ReferenceEquals(this, other))
+ return true;
+ if (Assignments.Count != other.Assignments.Count)
+ return false;
+
+ foreach (var (dimension, topologyId) in Assignments)
+ {
+ if (!other.Assignments.TryGetValue(dimension, out var otherId)
+ || !string.Equals(topologyId, otherId, StringComparison.Ordinal))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public override int GetHashCode()
+ {
+ var hash = new HashCode();
+ foreach (var (dimension, topologyId) in Assignments)
+ {
+ hash.Add(dimension, StringComparer.Ordinal);
+ hash.Add(topologyId, StringComparer.Ordinal);
+ }
+
+ return hash.ToHashCode();
+ }
+}
diff --git a/src/apps/tracker-api/Tracker.Infrastructure/Migrations/20260708045714_AddProductTopologySet.Designer.cs b/src/apps/tracker-api/Tracker.Infrastructure/Migrations/20260708045714_AddProductTopologySet.Designer.cs
new file mode 100644
index 00000000..0da13a79
--- /dev/null
+++ b/src/apps/tracker-api/Tracker.Infrastructure/Migrations/20260708045714_AddProductTopologySet.Designer.cs
@@ -0,0 +1,2783 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+using Tracker.Infrastructure.Persistence;
+
+#nullable disable
+
+namespace Tracker.Infrastructure.Migrations
+{
+ [DbContext(typeof(TrackerDbContext))]
+ [Migration("20260708045714_AddProductTopologySet")]
+ partial class AddProductTopologySet
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasDefaultSchema("tracker_governance")
+ .HasAnnotation("ProductVersion", "10.0.9")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "ltree");
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Tracker.Infrastructure.Persistence.Artifacts.Entities.EvidenceRecordRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("ArtifactId")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)")
+ .HasColumnName("artifact_id");
+
+ b.Property("ArtifactType")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)")
+ .HasColumnName("artifact_type");
+
+ b.Property("Classification")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("text")
+ .HasDefaultValue("")
+ .HasColumnName("classification");
+
+ b.Property("Content")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("content");
+
+ b.Property("ContentHash")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("text")
+ .HasDefaultValue("")
+ .HasColumnName("content_hash");
+
+ b.Property("CreatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("created_by");
+
+ b.Property("DeletedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property("DeletedBy")
+ .HasColumnType("text")
+ .HasColumnName("deleted_by");
+
+ b.Property("EvidenceType")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)")
+ .HasColumnName("evidence_type");
+
+ b.Property("Immutable")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("boolean")
+ .HasDefaultValue(false)
+ .HasColumnName("immutable");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean")
+ .HasColumnName("is_deleted");
+
+ b.Property("MetadataJson")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("metadata");
+
+ b.Property("Producer")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("text")
+ .HasDefaultValue("")
+ .HasColumnName("producer");
+
+ b.Property("ReferencesJson")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("jsonb")
+ .HasDefaultValue("[]")
+ .HasColumnName("references");
+
+ b.Property("RetentionPolicyRef")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("text")
+ .HasDefaultValue("")
+ .HasColumnName("retention_policy_ref");
+
+ b.Property("SourceProvider")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("text")
+ .HasDefaultValue("")
+ .HasColumnName("source_provider");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasMaxLength(20)
+ .HasColumnType("character varying(20)")
+ .HasColumnName("status");
+
+ b.Property("SubmittedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("submitted_at");
+
+ b.Property("SubmittedBy")
+ .HasColumnType("uuid")
+ .HasColumnName("submitted_by");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("tenant_id");
+
+ b.Property("UpdatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UpdatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("updated_by");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Classification");
+
+ b.HasIndex("TenantId");
+
+ b.HasIndex("ArtifactType", "ArtifactId");
+
+ b.ToTable("evidence_records", "tracker_governance");
+ });
+
+ modelBuilder.Entity("Tracker.Infrastructure.Persistence.Audit.Entities.AuditEntryRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("Action")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasColumnName("action");
+
+ b.Property("ActorId")
+ .HasColumnType("uuid")
+ .HasColumnName("actor_id");
+
+ b.Property("ChangesJson")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("changes");
+
+ b.Property("CorrelationId")
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)")
+ .HasColumnName("correlation_id");
+
+ b.Property("CreatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("created_by");
+
+ b.Property("DeletedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property("DeletedBy")
+ .HasColumnType("text")
+ .HasColumnName("deleted_by");
+
+ b.Property("EntityId")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)")
+ .HasColumnName("entity_id");
+
+ b.Property("EntityType")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)")
+ .HasColumnName("entity_type");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean")
+ .HasColumnName("is_deleted");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("tenant_id");
+
+ b.Property("TimestampUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("timestamp");
+
+ b.Property("UpdatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UpdatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("updated_by");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ActorId");
+
+ b.HasIndex("TenantId");
+
+ b.HasIndex("EntityType", "EntityId");
+
+ b.ToTable("audit_entries", "tracker_governance");
+ });
+
+ modelBuilder.Entity("Tracker.Infrastructure.Persistence.Design.Entities.TechnicalBlueprintRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("CreatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("created_by");
+
+ b.Property("DeletedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property("DeletedBy")
+ .HasColumnType("text")
+ .HasColumnName("deleted_by");
+
+ b.Property("FacetsJson")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("facets");
+
+ b.Property("InitiativeId")
+ .HasColumnType("uuid")
+ .HasColumnName("initiative_id");
+
+ b.Property("InitiativeType")
+ .IsRequired()
+ .HasMaxLength(20)
+ .HasColumnType("character varying(20)")
+ .HasColumnName("initiative_type");
+
+ b.Property("IntegrationAdrRef")
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)")
+ .HasColumnName("integration_adr_ref");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean")
+ .HasColumnName("is_deleted");
+
+ b.Property("Notes")
+ .HasColumnType("text")
+ .HasColumnName("notes");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasMaxLength(16)
+ .HasColumnType("character varying(16)")
+ .HasColumnName("status");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("tenant_id");
+
+ b.Property("UpdatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UpdatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("updated_by");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InitiativeId")
+ .IsUnique();
+
+ b.HasIndex("TenantId");
+
+ b.ToTable("technical_blueprints", "tracker_governance");
+ });
+
+ modelBuilder.Entity("Tracker.Infrastructure.Persistence.Discovery.Entities.InitiativeRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("ApprovalComplete")
+ .HasColumnType("boolean")
+ .HasColumnName("approval_complete");
+
+ b.Property("ApprovedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("approved_at");
+
+ b.Property("ApprovedBy")
+ .HasColumnType("uuid")
+ .HasColumnName("approved_by");
+
+ b.Property("BusinessCaseJson")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("business_case");
+
+ b.Property("Code")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)")
+ .HasColumnName("code");
+
+ b.Property("ConstituentsJson")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("constituents");
+
+ b.Property("CreatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("created_by");
+
+ b.Property("DeletedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property("DeletedBy")
+ .HasColumnType("text")
+ .HasColumnName("deleted_by");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("description");
+
+ b.Property("FacetsJson")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("facets");
+
+ b.Property("FunctionalBriefJson")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("functional_brief");
+
+ b.Property("InitiativeType")
+ .IsRequired()
+ .HasMaxLength(20)
+ .HasColumnType("character varying(20)")
+ .HasColumnName("initiative_type");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean")
+ .HasColumnName("is_deleted");
+
+ b.Property("MetadataJson")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("metadata");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasColumnName("name");
+
+ b.Property("Origin")
+ .IsRequired()
+ .HasMaxLength(20)
+ .HasColumnType("character varying(20)")
+ .HasColumnName("origin");
+
+ b.Property("OriginRef")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasColumnName("origin_ref");
+
+ b.Property("ProductScopeJson")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("product_scope");
+
+ b.Property("RejectionReason")
+ .HasColumnType("text")
+ .HasColumnName("rejection_reason");
+
+ b.Property("RequiredApproversJson")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("required_approvers");
+
+ b.Property("StakeholdersJson")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("stakeholders");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasMaxLength(20)
+ .HasColumnType("character varying(20)")
+ .HasColumnName("status");
+
+ b.Property("SupersededByInitiativeId")
+ .HasColumnType("uuid")
+ .HasColumnName("superseded_by_initiative_id");
+
+ b.Property("TechnicalBriefJson")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("technical_brief");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("tenant_id");
+
+ b.Property("TypeFrozen")
+ .HasColumnType("boolean")
+ .HasColumnName("type_frozen");
+
+ b.Property("UpdatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UpdatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("updated_by");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Status");
+
+ b.HasIndex("TenantId");
+
+ b.HasIndex("TenantId", "Code");
+
+ b.ToTable("initiatives", "tracker_governance");
+ });
+
+ modelBuilder.Entity("Tracker.Infrastructure.Persistence.Discovery.Entities.OpportunityRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("Code")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)")
+ .HasColumnName("code");
+
+ b.Property("ConsolidatedInitiativeId")
+ .HasColumnType("uuid")
+ .HasColumnName("consolidated_initiative_id");
+
+ b.Property("ContextJson")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("jsonb")
+ .HasDefaultValue("{}")
+ .HasColumnName("context");
+
+ b.Property("CreatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("created_by");
+
+ b.Property("CustomFieldsJson")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("jsonb")
+ .HasDefaultValue("{}")
+ .HasColumnName("custom_fields");
+
+ b.Property("DeletedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property("DeletedBy")
+ .HasColumnType("text")
+ .HasColumnName("deleted_by");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("description");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean")
+ .HasColumnName("is_deleted");
+
+ b.Property("MetadataJson")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("metadata");
+
+ b.Property("Priority")
+ .IsRequired()
+ .HasMaxLength(20)
+ .HasColumnType("character varying(20)")
+ .HasColumnName("priority");
+
+ b.Property("PromotedInitiativeId")
+ .HasColumnType("uuid")
+ .HasColumnName("promoted_initiative_id");
+
+ b.Property("Source")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)")
+ .HasColumnName("source");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasMaxLength(20)
+ .HasColumnType("character varying(20)")
+ .HasColumnName("status");
+
+ b.Property("TenantId")
+ .HasColumnType("uuid")
+ .HasColumnName("tenant_id");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasColumnName("title");
+
+ b.Property("UpdatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UpdatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("updated_by");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Status");
+
+ b.HasIndex("TenantId");
+
+ b.HasIndex("TenantId", "Code")
+ .IsUnique();
+
+ b.ToTable("opportunities", "tracker_governance");
+ });
+
+ modelBuilder.Entity("Tracker.Infrastructure.Persistence.Geo.Entities.CatalogReleaseRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("Code")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("code");
+
+ b.Property("CreatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("created_by");
+
+ b.Property("PublishedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("published_at");
+
+ b.Property("Source")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasColumnName("source");
+
+ b.Property("UpdatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UpdatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("updated_by");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Code")
+ .IsUnique();
+
+ b.ToTable("catalog_release", "geo");
+ });
+
+ modelBuilder.Entity("Tracker.Infrastructure.Persistence.Geo.Entities.CountryRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("CreatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("created_by");
+
+ b.Property("DefaultCurrencyCode")
+ .HasMaxLength(3)
+ .HasColumnType("character varying(3)")
+ .HasColumnName("default_currency_code");
+
+ b.Property("IsActive")
+ .HasColumnType("boolean")
+ .HasColumnName("is_active");
+
+ b.Property("IsoAlpha2")
+ .IsRequired()
+ .HasMaxLength(2)
+ .HasColumnType("character(2)")
+ .HasColumnName("iso_alpha2")
+ .IsFixedLength();
+
+ b.Property("IsoAlpha3")
+ .IsRequired()
+ .HasMaxLength(3)
+ .HasColumnType("character(3)")
+ .HasColumnName("iso_alpha3")
+ .IsFixedLength();
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(96)
+ .HasColumnType("character varying(96)")
+ .HasColumnName("name");
+
+ b.Property("NumericCode")
+ .HasMaxLength(3)
+ .HasColumnType("character(3)")
+ .HasColumnName("numeric_code")
+ .IsFixedLength();
+
+ b.Property("UpdatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UpdatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("updated_by");
+
+ b.HasKey("Id");
+
+ b.HasIndex("IsoAlpha2")
+ .IsUnique();
+
+ b.HasIndex("IsoAlpha3")
+ .IsUnique();
+
+ b.ToTable("country", "geo");
+ });
+
+ modelBuilder.Entity("Tracker.Infrastructure.Persistence.Geo.Entities.CurrencyRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("Code")
+ .IsRequired()
+ .HasMaxLength(3)
+ .HasColumnType("character varying(3)")
+ .HasColumnName("code");
+
+ b.Property("CreatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("created_by");
+
+ b.Property("IsActive")
+ .HasColumnType("boolean")
+ .HasColumnName("is_active");
+
+ b.Property("MinorUnits")
+ .HasColumnType("smallint")
+ .HasColumnName("minor_units");
+
+ b.Property("Symbol")
+ .IsRequired()
+ .HasMaxLength(8)
+ .HasColumnType("character varying(8)")
+ .HasColumnName("symbol");
+
+ b.Property("UpdatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UpdatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("updated_by");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Code")
+ .IsUnique();
+
+ b.ToTable("currency", "geo");
+ });
+
+ modelBuilder.Entity("Tracker.Infrastructure.Persistence.Geo.Entities.ExchangeRateRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("BaseCode")
+ .IsRequired()
+ .HasMaxLength(3)
+ .HasColumnType("character varying(3)")
+ .HasColumnName("base_code");
+
+ b.Property("CreatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("created_by");
+
+ b.Property("QuoteCode")
+ .IsRequired()
+ .HasMaxLength(3)
+ .HasColumnType("character varying(3)")
+ .HasColumnName("quote_code");
+
+ b.Property("Rate")
+ .HasColumnType("numeric(20,10)")
+ .HasColumnName("rate");
+
+ b.Property("Source")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasColumnName("source");
+
+ b.Property("UpdatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UpdatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("updated_by");
+
+ b.Property("ValidFrom")
+ .HasColumnType("date")
+ .HasColumnName("valid_from");
+
+ b.Property("ValidTo")
+ .HasColumnType("date")
+ .HasColumnName("valid_to");
+
+ b.HasKey("Id");
+
+ b.HasIndex("BaseCode", "QuoteCode", "ValidFrom")
+ .HasDatabaseName("ix_exchange_rate_pair");
+
+ b.ToTable("exchange_rate", "geo");
+ });
+
+ modelBuilder.Entity("Tracker.Infrastructure.Persistence.Geo.Entities.LanguageRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("Code")
+ .IsRequired()
+ .HasMaxLength(8)
+ .HasColumnType("character varying(8)")
+ .HasColumnName("code");
+
+ b.Property("CreatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("created_by");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasColumnName("name");
+
+ b.Property("UpdatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UpdatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("updated_by");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Code")
+ .IsUnique();
+
+ b.ToTable("language", "geo");
+ });
+
+ modelBuilder.Entity("Tracker.Infrastructure.Persistence.Geo.Entities.LocaleProfileRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("Code")
+ .IsRequired()
+ .HasMaxLength(16)
+ .HasColumnType("character varying(16)")
+ .HasColumnName("code");
+
+ b.Property("CountryId")
+ .HasColumnType("uuid")
+ .HasColumnName("country_id");
+
+ b.Property("CreatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("created_by");
+
+ b.Property("DateFormat")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("date_format");
+
+ b.Property("FirstDayOfWeek")
+ .HasColumnType("smallint")
+ .HasColumnName("first_day_of_week");
+
+ b.Property("Name")
+ .HasMaxLength(96)
+ .HasColumnType("character varying(96)")
+ .HasColumnName("name");
+
+ b.Property("NumberFormat")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("number_format");
+
+ b.Property("UpdatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UpdatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("updated_by");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Code")
+ .IsUnique();
+
+ b.HasIndex("CountryId");
+
+ b.ToTable("locale_profile", "geo");
+ });
+
+ modelBuilder.Entity("Tracker.Infrastructure.Persistence.Geo.Entities.LocationCodeRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("LocationId")
+ .HasColumnType("uuid")
+ .HasColumnName("location_id");
+
+ b.Property("Scheme")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("scheme");
+
+ b.Property("Value")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("value");
+
+ b.HasKey("Id");
+
+ b.HasIndex("LocationId", "Scheme")
+ .IsUnique();
+
+ b.HasIndex("Scheme", "Value")
+ .IsUnique();
+
+ b.ToTable("location_code", "geo");
+ });
+
+ modelBuilder.Entity("Tracker.Infrastructure.Persistence.Geo.Entities.LocationI18nRecord", b =>
+ {
+ b.Property("LocationId")
+ .HasColumnType("uuid")
+ .HasColumnName("location_id");
+
+ b.Property("Locale")
+ .HasMaxLength(16)
+ .HasColumnType("character varying(16)")
+ .HasColumnName("locale");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(160)
+ .HasColumnType("character varying(160)")
+ .HasColumnName("name");
+
+ b.HasKey("LocationId", "Locale");
+
+ b.ToTable("location_i18n", "geo");
+ });
+
+ modelBuilder.Entity("Tracker.Infrastructure.Persistence.Geo.Entities.LocationLevelRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("Code")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("code");
+
+ b.Property("CountryId")
+ .HasColumnType("uuid")
+ .HasColumnName("country_id");
+
+ b.Property("CreatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("created_by");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasColumnName("name");
+
+ b.Property("Ordinal")
+ .HasColumnType("smallint")
+ .HasColumnName("ordinal");
+
+ b.Property("UpdatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UpdatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("updated_by");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CountryId", "Ordinal")
+ .IsUnique();
+
+ b.ToTable("location_level", "geo");
+ });
+
+ modelBuilder.Entity("Tracker.Infrastructure.Persistence.Geo.Entities.LocationRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("CatalogReleaseId")
+ .HasColumnType("uuid")
+ .HasColumnName("catalog_release_id");
+
+ b.Property("CountryId")
+ .HasColumnType("uuid")
+ .HasColumnName("country_id");
+
+ b.Property("CreatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("created_by");
+
+ b.Property("IsActive")
+ .HasColumnType("boolean")
+ .HasColumnName("is_active");
+
+ b.Property("LevelId")
+ .HasColumnType("uuid")
+ .HasColumnName("level_id");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(160)
+ .HasColumnType("character varying(160)")
+ .HasColumnName("name");
+
+ b.Property("ParentId")
+ .HasColumnType("uuid")
+ .HasColumnName("parent_id");
+
+ b.Property("Path")
+ .IsRequired()
+ .HasColumnType("ltree")
+ .HasColumnName("path");
+
+ b.Property("SupersedesId")
+ .HasColumnType("uuid")
+ .HasColumnName("supersedes_id");
+
+ b.Property("UpdatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UpdatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("updated_by");
+
+ b.Property("ValidFrom")
+ .HasColumnType("date")
+ .HasColumnName("valid_from");
+
+ b.Property("ValidTo")
+ .HasColumnType("date")
+ .HasColumnName("valid_to");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ParentId")
+ .HasDatabaseName("ix_location_parent");
+
+ b.HasIndex("Path")
+ .HasDatabaseName("ix_location_path");
+
+ NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Path"), "gist");
+
+ b.HasIndex("CountryId", "ParentId")
+ .HasDatabaseName("ix_location_country_parent");
+
+ b.ToTable("location", "geo");
+ });
+
+ modelBuilder.Entity("Tracker.Infrastructure.Persistence.Geo.Entities.TaxRateRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("CreatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("created_by");
+
+ b.Property("LocationId")
+ .HasColumnType("uuid")
+ .HasColumnName("location_id");
+
+ b.Property("OperationType")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("operation_type");
+
+ b.Property("Rate")
+ .HasColumnType("numeric(6,4)")
+ .HasColumnName("rate");
+
+ b.Property("TaxSchemeId")
+ .HasColumnType("uuid")
+ .HasColumnName("tax_scheme_id");
+
+ b.Property("UpdatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UpdatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("updated_by");
+
+ b.Property("ValidFrom")
+ .HasColumnType("date")
+ .HasColumnName("valid_from");
+
+ b.Property("ValidTo")
+ .HasColumnType("date")
+ .HasColumnName("valid_to");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TaxSchemeId", "LocationId", "ValidFrom")
+ .HasDatabaseName("ix_tax_rate_scheme_location");
+
+ b.ToTable("tax_rate", "geo");
+ });
+
+ modelBuilder.Entity("Tracker.Infrastructure.Persistence.Geo.Entities.TaxSchemeRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("Code")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("code");
+
+ b.Property("CountryId")
+ .HasColumnType("uuid")
+ .HasColumnName("country_id");
+
+ b.Property("CreatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("created_by");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(96)
+ .HasColumnType("character varying(96)")
+ .HasColumnName("name");
+
+ b.Property("UpdatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UpdatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("updated_by");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CountryId", "Code")
+ .IsUnique();
+
+ b.ToTable("tax_scheme", "geo");
+ });
+
+ modelBuilder.Entity("Tracker.Infrastructure.Persistence.Geo.Entities.TimeZoneRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("CountryCode")
+ .HasMaxLength(2)
+ .HasColumnType("character varying(2)")
+ .HasColumnName("country_code");
+
+ b.Property("CreatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("created_by");
+
+ b.Property("IanaId")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasColumnName("iana_id");
+
+ b.Property("UpdatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at");
+
+ b.Property("UpdatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("updated_by");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CountryCode");
+
+ b.HasIndex("IanaId")
+ .IsUnique();
+
+ b.ToTable("time_zone", "geo");
+ });
+
+ modelBuilder.Entity("Tracker.Infrastructure.Persistence.Governance.Entities.ArtifactFieldSchemaRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("ArtifactType")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)")
+ .HasColumnName("artifact_type");
+
+ b.Property("CreatedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("CreatedBy")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("created_by");
+
+ b.Property("CustomFieldsJson")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("jsonb")
+ .HasDefaultValue("[]")
+ .HasColumnName("custom_fields");
+
+ b.Property