diff --git a/docs/audit/tracker-gap-reference-catalog.md b/docs/audit/tracker-gap-reference-catalog.md
index 8da000eb..a7de74ab 100644
--- a/docs/audit/tracker-gap-reference-catalog.md
+++ b/docs/audit/tracker-gap-reference-catalog.md
@@ -247,7 +247,7 @@ This catalog explains each gap: problem, purpose, evidence, closure criteria, an
- **Acceptance criteria:**
- [ ] Evidence is a first-class immutable entity with lineage/integrity, referenced by gate decisions.
- **Dependencies:** [CR-13](#cr-13), [CR-15](#cr-15).
- - **Status:** `IN-PROGRESS` — `EvidenceRecord` es un agregado persistido (Create/Verify/Reject + hash); falta el Evidence Graph (aristas de linaje + integridad/clasificación/retención).
+ - **Status:** `DONE` — `EvidenceRecord` como nodo de Evidence Graph: procedencia/integridad de primera clase (sourceProvider/producer/contentHash/classification/retentionPolicyRef/immutable), aristas `references[]` vía `AddReference` (dedup) + `Seal`, endpoint `POST /{id}/references`, migración aditiva `AddEvidenceGraphFields`; 458 tests.
#### CR-15
diff --git a/docs/audit/tracker-gap-tracking.md b/docs/audit/tracker-gap-tracking.md
index 822e76c6..0d20f7f8 100644
--- a/docs/audit/tracker-gap-tracking.md
+++ b/docs/audit/tracker-gap-tracking.md
@@ -27,7 +27,7 @@ This board is the single source of truth for Tracker technical debt, gaps, oppor
| [`CR-21`](./tracker-gap-reference-catalog.md#cr-21) | Las 5 pantallas canónicas están ruteadas y accesibles (Gate Decision/Evaluation = `GateEvaluationMd3` ya existe — la nota "pendiente" estaba desactualizada); único faltante: un **browser de ADRs** dedicado (hoy los ADRs son solo tags en blueprints). | `WEB` | Cross | P1 | S | `IN-PROGRESS` |
| [`GAP-010`](./tracker-gap-reference-catalog.md#gap-010) | **Hecho:** existe `reference/knowledge/domain/PRODUCT_VISION.md` (EN) con navegación bilingüe a `PRODUCT_VISION.es.md`. | `Docs` | Cross | P1 | M | `DONE` |
| [`CR-10`](./tracker-gap-reference-catalog.md#cr-10) | Pantalla + endpoint de navegación de arquitectura (independiente del SDLC) existen para topologías/blueprints/evidencia/providers; faltan registro/browser de ADRs (estado/supersesión) y sync respaldado por Core (el catálogo hoy es estático). | `WEB/Backend` | Cross | P1 | M | `IN-PROGRESS` |
-| [`CR-14`](./tracker-gap-reference-catalog.md#cr-14) | "evidencia = conteos enteros" desactualizado: `EvidenceRecord` es un agregado persistido (Create/Verify/Reject + hash). Falta el Evidence **Graph**: aristas de linaje/referencia y campos de integridad/clasificación/retención en el agregado (hoy solo en mock frontend + doc de diseño). | `Backend` | Cross | P1 | M | `IN-PROGRESS` |
+| [`CR-14`](./tracker-gap-reference-catalog.md#cr-14) | **Hecho:** `EvidenceRecord` es ahora un nodo de Evidence **Graph** — campos de primera clase `sourceProvider`/`producer`/`contentHash`/`classification`(validada)/`retentionPolicyRef`/`immutable`, aristas de linaje `references[]` con operación de dominio `AddReference` (dedup) + `Seal`, comando/endpoint `POST /{id}/references`, migración aditiva `AddEvidenceGraphFields`. Build 0-warnings, 458 tests (+15). | `Backend` | Cross | P1 | M | `DONE` |
| [`CR-15`](./tracker-gap-reference-catalog.md#cr-15) | Solo existe un display estático de Provider Connections (catálogo de referencia + panel UI). El agregado `ProviderConnection`, los ports por capacidad, el mapeo ACL y los adaptadores siguen **sin construir** en Domain/Application/Infrastructure. | `Backend/Integration` | Cross | P1 | M | `IN-PROGRESS` |
| [`GAP-019`](./tracker-gap-reference-catalog.md#gap-019) | **Hecho:** requisitos Discovery desglosados a detalle implementable — PRD §2 REQ-DIS-01…13, cada uno con cláusula de aceptación, + catálogo de artefactos por gate (`functional-scope.md` §6). Sin user-stories atómicas, coherente con "sin backlog propio". | `Docs` | Cross | P1 | M | `DONE` |
| [`CR-27`](./tracker-gap-reference-catalog.md#cr-27) | Phantom navigation: `aigov` + 4 nav items have no screen | `WEB` | Cross | P2 | S | `PENDING` |
diff --git a/src/apps/tracker-api/Tracker.Application/Artifacts/EvidenceRecord/Commands/AddEvidenceReference/AddEvidenceReferenceCommand.cs b/src/apps/tracker-api/Tracker.Application/Artifacts/EvidenceRecord/Commands/AddEvidenceReference/AddEvidenceReferenceCommand.cs
new file mode 100644
index 00000000..6f4b3f53
--- /dev/null
+++ b/src/apps/tracker-api/Tracker.Application/Artifacts/EvidenceRecord/Commands/AddEvidenceReference/AddEvidenceReferenceCommand.cs
@@ -0,0 +1,13 @@
+using Tracker.Application.Abstractions.Messaging;
+
+namespace Tracker.Application.Artifacts.EvidenceRecord.Commands.AddEvidenceReference;
+
+///
+/// Adds a lineage edge from an evidence node to another Evidence Graph node
+/// (evidence, gate decision, ADR, context, …).
+///
+public sealed record AddEvidenceReferenceCommand(
+ Guid EvidenceId,
+ Guid TenantId,
+ string Reference
+) : ICommand;
diff --git a/src/apps/tracker-api/Tracker.Application/Artifacts/EvidenceRecord/Commands/AddEvidenceReference/AddEvidenceReferenceCommandHandler.cs b/src/apps/tracker-api/Tracker.Application/Artifacts/EvidenceRecord/Commands/AddEvidenceReference/AddEvidenceReferenceCommandHandler.cs
new file mode 100644
index 00000000..42c52573
--- /dev/null
+++ b/src/apps/tracker-api/Tracker.Application/Artifacts/EvidenceRecord/Commands/AddEvidenceReference/AddEvidenceReferenceCommandHandler.cs
@@ -0,0 +1,41 @@
+using Tracker.Application.Abstractions.Messaging;
+using Tracker.Domain.Kernel;
+
+namespace Tracker.Application.Artifacts.EvidenceRecord.Commands.AddEvidenceReference;
+
+internal sealed class AddEvidenceReferenceCommandHandler
+ : ICommandHandler
+{
+ private readonly Tracker.Domain.Artifacts.EvidenceRecord.IEvidenceRecordRepository _repository;
+ private readonly IUnitOfWork _unitOfWork;
+
+ public AddEvidenceReferenceCommandHandler(
+ Tracker.Domain.Artifacts.EvidenceRecord.IEvidenceRecordRepository repository,
+ IUnitOfWork unitOfWork)
+ {
+ _repository = repository;
+ _unitOfWork = unitOfWork;
+ }
+
+ public async Task Handle(
+ AddEvidenceReferenceCommand request,
+ CancellationToken cancellationToken)
+ {
+ var record = await _repository.GetByIdAsync(request.EvidenceId, cancellationToken);
+
+ if (record is null)
+ return Result.Failure("EvidenceRecord.NotFound");
+
+ if (record.TenantId != request.TenantId)
+ return Result.Failure("EvidenceRecord.AccessDenied");
+
+ var result = record.AddReference(request.Reference);
+ if (result.IsFailure)
+ return Result.Failure(result.Error);
+
+ await _repository.UpdateAsync(record, cancellationToken);
+ await _unitOfWork.SaveEntitiesAsync(cancellationToken);
+
+ return Result.Success();
+ }
+}
diff --git a/src/apps/tracker-api/Tracker.Application/Artifacts/EvidenceRecord/Commands/CreateEvidenceRecord/CreateEvidenceRecordCommand.cs b/src/apps/tracker-api/Tracker.Application/Artifacts/EvidenceRecord/Commands/CreateEvidenceRecord/CreateEvidenceRecordCommand.cs
index 56466769..02aa2893 100644
--- a/src/apps/tracker-api/Tracker.Application/Artifacts/EvidenceRecord/Commands/CreateEvidenceRecord/CreateEvidenceRecordCommand.cs
+++ b/src/apps/tracker-api/Tracker.Application/Artifacts/EvidenceRecord/Commands/CreateEvidenceRecord/CreateEvidenceRecordCommand.cs
@@ -8,5 +8,10 @@ public sealed record CreateEvidenceRecordCommand(
string ArtifactId,
string EvidenceType,
string Content,
- Guid SubmittedBy
+ Guid SubmittedBy,
+ string SourceProvider = "",
+ string Producer = "",
+ string Classification = Tracker.Domain.Artifacts.EvidenceRecord.EvidenceClassification.Internal,
+ string RetentionPolicyRef = "",
+ IReadOnlyList? References = null
) : ICommand;
diff --git a/src/apps/tracker-api/Tracker.Application/Artifacts/EvidenceRecord/Commands/CreateEvidenceRecord/CreateEvidenceRecordCommandHandler.cs b/src/apps/tracker-api/Tracker.Application/Artifacts/EvidenceRecord/Commands/CreateEvidenceRecord/CreateEvidenceRecordCommandHandler.cs
index 838e174c..4afed2dc 100644
--- a/src/apps/tracker-api/Tracker.Application/Artifacts/EvidenceRecord/Commands/CreateEvidenceRecord/CreateEvidenceRecordCommandHandler.cs
+++ b/src/apps/tracker-api/Tracker.Application/Artifacts/EvidenceRecord/Commands/CreateEvidenceRecord/CreateEvidenceRecordCommandHandler.cs
@@ -23,21 +23,30 @@ public async Task> Handle(
CreateEvidenceRecordCommand request,
CancellationToken cancellationToken)
{
+ // Compute an integrity hash of the content so evidence is tamper-evident, then
+ // promote it to the first-class ContentHash node field of the Evidence Graph.
+ var contentSha256 = ComputeSha256Hex(request.Content);
+
var result = Tracker.Domain.Artifacts.EvidenceRecord.EvidenceRecord.Create(
request.TenantId,
request.ArtifactType,
request.ArtifactId,
request.EvidenceType,
request.Content,
- request.SubmittedBy);
+ request.SubmittedBy,
+ request.SourceProvider,
+ request.Producer,
+ contentSha256,
+ request.Classification,
+ request.RetentionPolicyRef,
+ request.References?.ToList());
if (result.IsFailure)
return Result.Failure(result.Error);
var evidence = result.Value;
- // Carry an integrity hash of the content so evidence is tamper-evident.
- var contentSha256 = ComputeSha256Hex(request.Content);
+ // Keep the legacy metadata copy for backward compatibility with existing readers.
var metadata = new Dictionary(evidence.Metadata)
{
["contentSha256"] = contentSha256
diff --git a/src/apps/tracker-api/Tracker.Application/Artifacts/EvidenceRecord/DTOs/EvidenceRecordDto.cs b/src/apps/tracker-api/Tracker.Application/Artifacts/EvidenceRecord/DTOs/EvidenceRecordDto.cs
index 240385cf..b75deb2f 100644
--- a/src/apps/tracker-api/Tracker.Application/Artifacts/EvidenceRecord/DTOs/EvidenceRecordDto.cs
+++ b/src/apps/tracker-api/Tracker.Application/Artifacts/EvidenceRecord/DTOs/EvidenceRecordDto.cs
@@ -10,4 +10,13 @@ public sealed class EvidenceRecordDto
public string Status { get; init; } = string.Empty;
public Guid SubmittedBy { get; init; }
public DateTime SubmittedAtUtc { get; init; }
+
+ // Evidence Graph node: provenance / integrity / lineage.
+ public string SourceProvider { get; init; } = string.Empty;
+ public string Producer { get; init; } = string.Empty;
+ public string ContentHash { get; init; } = string.Empty;
+ public string Classification { get; init; } = string.Empty;
+ public string RetentionPolicyRef { get; init; } = string.Empty;
+ public IReadOnlyList References { get; init; } = new List();
+ public bool Immutable { get; init; }
}
diff --git a/src/apps/tracker-api/Tracker.Application/Artifacts/EvidenceRecord/Queries/GetEvidenceRecord/GetEvidenceRecordQueryHandler.cs b/src/apps/tracker-api/Tracker.Application/Artifacts/EvidenceRecord/Queries/GetEvidenceRecord/GetEvidenceRecordQueryHandler.cs
index 3ab128cd..d2ac3ea1 100644
--- a/src/apps/tracker-api/Tracker.Application/Artifacts/EvidenceRecord/Queries/GetEvidenceRecord/GetEvidenceRecordQueryHandler.cs
+++ b/src/apps/tracker-api/Tracker.Application/Artifacts/EvidenceRecord/Queries/GetEvidenceRecord/GetEvidenceRecordQueryHandler.cs
@@ -32,7 +32,14 @@ public GetEvidenceRecordQueryHandler(Tracker.Domain.Artifacts.EvidenceRecord.IEv
Content = record.Content,
Status = record.Status,
SubmittedBy = record.SubmittedBy,
- SubmittedAtUtc = record.SubmittedAtUtc
+ SubmittedAtUtc = record.SubmittedAtUtc,
+ SourceProvider = record.SourceProvider,
+ Producer = record.Producer,
+ ContentHash = record.ContentHash,
+ Classification = record.Classification,
+ RetentionPolicyRef = record.RetentionPolicyRef,
+ References = record.References,
+ Immutable = record.Immutable
};
}
}
diff --git a/src/apps/tracker-api/Tracker.Domain/Artifacts/EvidenceRecord/EvidenceRecord.cs b/src/apps/tracker-api/Tracker.Domain/Artifacts/EvidenceRecord/EvidenceRecord.cs
index e68cf5d8..093f6ab8 100644
--- a/src/apps/tracker-api/Tracker.Domain/Artifacts/EvidenceRecord/EvidenceRecord.cs
+++ b/src/apps/tracker-api/Tracker.Domain/Artifacts/EvidenceRecord/EvidenceRecord.cs
@@ -17,14 +17,31 @@ private EvidenceRecord(EvidenceRecordProps props) : base(props) { }
public DateTime SubmittedAtUtc => Props.SubmittedAtUtc;
public Dictionary Metadata => Props.Metadata;
+ public string SourceProvider => Props.SourceProvider;
+ public string Producer => Props.Producer;
+ public string ContentHash => Props.ContentHash;
+ public string Classification => Props.Classification;
+ public string RetentionPolicyRef => Props.RetentionPolicyRef;
+ public IReadOnlyList References => Props.References;
+ public bool Immutable => Props.Immutable;
+
public static Result Create(
Guid tenantId,
string artifactType,
string artifactId,
string evidenceType,
string content,
- Guid submittedBy)
+ Guid submittedBy,
+ string sourceProvider = "",
+ string producer = "",
+ string contentHash = "",
+ string classification = EvidenceClassification.Internal,
+ string retentionPolicyRef = "",
+ List? references = null)
{
+ if (!EvidenceClassification.IsValid(classification))
+ return Result.Failure("EvidenceRecord.InvalidClassification");
+
var props = new EvidenceRecordProps
{
Id = TrackerId.Create(),
@@ -37,12 +54,52 @@ public static Result Create(
SubmittedBy = submittedBy,
SubmittedAtUtc = DateTime.UtcNow,
Metadata = new(),
+ SourceProvider = sourceProvider,
+ Producer = producer,
+ ContentHash = contentHash,
+ Classification = classification,
+ RetentionPolicyRef = retentionPolicyRef,
+ References = references is null ? new() : new List(references),
+ Immutable = false,
Audit = new AuditProps(submittedBy.ToString(), DateTime.UtcNow)
};
return Result.Success(new EvidenceRecord(props));
}
+ ///
+ /// Adds a lineage edge from this evidence node to another graph node (evidence,
+ /// gate decision, ADR, context, …). Idempotent: adding a reference that is already
+ /// present is a no-op success, so the adjacency list stays a set of distinct edges.
+ ///
+ public Result AddReference(string reference)
+ {
+ if (string.IsNullOrWhiteSpace(reference))
+ return Result.Failure("EvidenceRecord.EmptyReference");
+
+ if (Props.References.Contains(reference))
+ return Result.Success();
+
+ var references = new List(Props.References) { reference };
+ SetProps(Props with { References = references });
+ TrackingState.MarkAsDirty();
+ return Result.Success();
+ }
+
+ ///
+ /// Seals the evidence node: once sealed the content and integrity hash are frozen by
+ /// convention. Idempotent — sealing an already-sealed node is a no-op success.
+ ///
+ public Result Seal()
+ {
+ if (Props.Immutable)
+ return Result.Success();
+
+ SetProps(Props with { Immutable = true });
+ TrackingState.MarkAsDirty();
+ return Result.Success();
+ }
+
public Result Verify(Guid verifiedBy)
{
if (Props.Status != "submitted")
diff --git a/src/apps/tracker-api/Tracker.Domain/Artifacts/EvidenceRecord/EvidenceRecordProps.cs b/src/apps/tracker-api/Tracker.Domain/Artifacts/EvidenceRecord/EvidenceRecordProps.cs
index 176f9d02..3ab211b9 100644
--- a/src/apps/tracker-api/Tracker.Domain/Artifacts/EvidenceRecord/EvidenceRecordProps.cs
+++ b/src/apps/tracker-api/Tracker.Domain/Artifacts/EvidenceRecord/EvidenceRecordProps.cs
@@ -16,7 +16,21 @@ public sealed record EvidenceRecordProps : IProps
public DateTime SubmittedAtUtc { get; init; }
public Dictionary Metadata { get; init; } = new();
+ // Evidence Graph: provenance / integrity fields promoting the record to a first-class node.
+ public string SourceProvider { get; init; } = string.Empty;
+ public string Producer { get; init; } = string.Empty;
+ public string ContentHash { get; init; } = string.Empty;
+ public string Classification { get; init; } = string.Empty;
+ public string RetentionPolicyRef { get; init; } = string.Empty;
+
+ // Evidence Graph: lineage edges (adjacency list) to other evidence, gate decisions, ADRs, contexts.
+ public List References { get; init; } = new();
+
+ // Evidence Graph: integrity seal — once true the content/hash are frozen by convention.
+ public bool Immutable { get; init; }
+
/// Copy hook required by the Core shared-kernel IProps : ICloneable. Explicit
- /// interface impl because records cannot declare a member literally named Clone (CS8859).
- object ICloneable.Clone() => this with { };
+ /// interface impl because records cannot declare a member literally named Clone (CS8859).
+ /// The list is copied so clones never share the mutable backing store.
+ object ICloneable.Clone() => this with { References = new List(References) };
}
diff --git a/src/apps/tracker-api/Tracker.Domain/Artifacts/EvidenceRecord/EvidenceValues.cs b/src/apps/tracker-api/Tracker.Domain/Artifacts/EvidenceRecord/EvidenceValues.cs
new file mode 100644
index 00000000..2b2b1451
--- /dev/null
+++ b/src/apps/tracker-api/Tracker.Domain/Artifacts/EvidenceRecord/EvidenceValues.cs
@@ -0,0 +1,18 @@
+namespace Tracker.Domain.Artifacts.EvidenceRecord;
+
+///
+/// Canonical, Tracker-owned evidence classification levels. This is the authoritative
+/// data-sensitivity taxonomy for an node in the Evidence Graph,
+/// governing how provenance/integrity of the artifact is disclosed and retained.
+///
+public static class EvidenceClassification
+{
+ public const string Public = "public";
+ public const string Internal = "internal";
+ public const string Confidential = "confidential";
+ public const string Restricted = "restricted";
+
+ /// True if is one of the canonical classification levels.
+ public static bool IsValid(string classification)
+ => classification is Public or Internal or Confidential or Restricted;
+}
diff --git a/src/apps/tracker-api/Tracker.Infrastructure/Migrations/20260708044013_AddEvidenceGraphFields.Designer.cs b/src/apps/tracker-api/Tracker.Infrastructure/Migrations/20260708044013_AddEvidenceGraphFields.Designer.cs
new file mode 100644
index 00000000..3ba2c789
--- /dev/null
+++ b/src/apps/tracker-api/Tracker.Infrastructure/Migrations/20260708044013_AddEvidenceGraphFields.Designer.cs
@@ -0,0 +1,2779 @@
+//
+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("20260708044013_AddEvidenceGraphFields")]
+ partial class AddEvidenceGraphFields
+ {
+ ///
+ 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("DeletedAtUtc")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("deleted_at");
+
+ b.Property("DeletedBy")
+ .HasColumnType("text")
+ .HasColumnName("deleted_by");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean")
+ .HasColumnName("is_deleted");
+
+ 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("TenantId", "ArtifactType")
+ .IsUnique();
+
+ b.ToTable("artifact_field_schemas", "tracker_governance");
+ });
+
+ modelBuilder.Entity("Tracker.Infrastructure.Persistence.Governance.Entities.GateDecisionRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id");
+
+ b.Property("ApprovalIdsJson")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("jsonb")
+ .HasDefaultValue("[]")
+ .HasColumnName("approval_ids");
+
+ b.Property