Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace StellaOps.Evidence.Persistence.EfCore.Context;
|
||||
|
||||
/// <summary>
|
||||
/// EF Core DbContext for Evidence module.
|
||||
/// This is a stub that will be scaffolded from the PostgreSQL database.
|
||||
/// </summary>
|
||||
public class EvidenceDbContext : DbContext
|
||||
{
|
||||
public EvidenceDbContext(DbContextOptions<EvidenceDbContext> options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.HasDefaultSchema("evidence");
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using StellaOps.Evidence.Persistence.Postgres;
|
||||
using StellaOps.Infrastructure.Postgres.Options;
|
||||
|
||||
namespace StellaOps.Evidence.Persistence.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for configuring Evidence persistence services.
|
||||
/// </summary>
|
||||
public static class EvidencePersistenceExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds Evidence PostgreSQL persistence services.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddEvidencePersistence(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration,
|
||||
string sectionName = "Postgres:Evidence")
|
||||
{
|
||||
services.Configure<PostgresOptions>(configuration.GetSection(sectionName));
|
||||
services.TryAddSingleton<EvidenceDataSource>();
|
||||
services.TryAddSingleton<PostgresEvidenceStoreFactory>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds Evidence PostgreSQL persistence services with explicit options.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddEvidencePersistence(
|
||||
this IServiceCollection services,
|
||||
Action<PostgresOptions> configureOptions)
|
||||
{
|
||||
services.Configure(configureOptions);
|
||||
services.TryAddSingleton<EvidenceDataSource>();
|
||||
services.TryAddSingleton<PostgresEvidenceStoreFactory>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 001_initial_schema.sql
|
||||
-- Sprint: 8100.0012.0002 - Unified Evidence Model
|
||||
-- Description: Creates the evidence schema and records table for unified evidence storage
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
-- Create schema
|
||||
CREATE SCHEMA IF NOT EXISTS evidence;
|
||||
|
||||
-- Evidence records table
|
||||
CREATE TABLE IF NOT EXISTS evidence.records (
|
||||
evidence_id TEXT PRIMARY KEY,
|
||||
subject_node_id TEXT NOT NULL,
|
||||
evidence_type SMALLINT NOT NULL,
|
||||
payload BYTEA NOT NULL,
|
||||
payload_schema_ver TEXT NOT NULL,
|
||||
external_cid TEXT,
|
||||
provenance JSONB NOT NULL,
|
||||
signatures JSONB NOT NULL DEFAULT '[]',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
tenant_id UUID NOT NULL
|
||||
);
|
||||
|
||||
-- Index for subject-based queries (most common access pattern)
|
||||
CREATE INDEX IF NOT EXISTS idx_evidence_subject
|
||||
ON evidence.records (subject_node_id, evidence_type);
|
||||
|
||||
-- Index for type-based queries with recency ordering
|
||||
CREATE INDEX IF NOT EXISTS idx_evidence_type
|
||||
ON evidence.records (evidence_type, created_at DESC);
|
||||
|
||||
-- Index for tenant-based queries with recency ordering
|
||||
CREATE INDEX IF NOT EXISTS idx_evidence_tenant
|
||||
ON evidence.records (tenant_id, created_at DESC);
|
||||
|
||||
-- Index for external CID lookups (when payload stored externally)
|
||||
CREATE INDEX IF NOT EXISTS idx_evidence_external_cid
|
||||
ON evidence.records (external_cid)
|
||||
WHERE external_cid IS NOT NULL;
|
||||
|
||||
-- Enable Row-Level Security
|
||||
ALTER TABLE evidence.records ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- RLS policy: tenant isolation
|
||||
CREATE POLICY evidence_tenant_isolation ON evidence.records
|
||||
USING (tenant_id = current_setting('app.tenant_id', true)::uuid);
|
||||
|
||||
-- Comment on table
|
||||
COMMENT ON TABLE evidence.records IS 'Unified evidence storage for content-addressed proof records';
|
||||
COMMENT ON COLUMN evidence.records.evidence_id IS 'Content-addressed identifier (sha256:{hex})';
|
||||
COMMENT ON COLUMN evidence.records.subject_node_id IS 'Content-addressed subject identifier this evidence applies to';
|
||||
COMMENT ON COLUMN evidence.records.evidence_type IS 'Type discriminator (1=Reachability, 2=Scan, 3=Policy, etc.)';
|
||||
COMMENT ON COLUMN evidence.records.payload IS 'Canonical JSON payload bytes';
|
||||
COMMENT ON COLUMN evidence.records.payload_schema_ver IS 'Schema version for payload format';
|
||||
COMMENT ON COLUMN evidence.records.external_cid IS 'Optional CID for large payloads stored externally';
|
||||
COMMENT ON COLUMN evidence.records.provenance IS 'Generation provenance (generator, timestamp, etc.)';
|
||||
COMMENT ON COLUMN evidence.records.signatures IS 'Cryptographic signatures attesting to this evidence';
|
||||
COMMENT ON COLUMN evidence.records.tenant_id IS 'Tenant identifier for multi-tenant isolation';
|
||||
@@ -0,0 +1,39 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Infrastructure.Postgres.Connections;
|
||||
using StellaOps.Infrastructure.Postgres.Options;
|
||||
|
||||
namespace StellaOps.Evidence.Persistence.Postgres;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL data source for the Evidence module.
|
||||
/// Manages connections with tenant context for evidence storage.
|
||||
/// </summary>
|
||||
public sealed class EvidenceDataSource : DataSourceBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Default schema name for Evidence tables.
|
||||
/// </summary>
|
||||
public const string DefaultSchemaName = "evidence";
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Evidence data source.
|
||||
/// </summary>
|
||||
public EvidenceDataSource(IOptions<PostgresOptions> options, ILogger<EvidenceDataSource> logger)
|
||||
: base(CreateOptions(options.Value), logger)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string ModuleName => "Evidence";
|
||||
|
||||
private static PostgresOptions CreateOptions(PostgresOptions baseOptions)
|
||||
{
|
||||
// Use default schema if not specified
|
||||
if (string.IsNullOrWhiteSpace(baseOptions.SchemaName))
|
||||
{
|
||||
baseOptions.SchemaName = DefaultSchemaName;
|
||||
}
|
||||
return baseOptions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using NpgsqlTypes;
|
||||
using StellaOps.Evidence.Core;
|
||||
using StellaOps.Infrastructure.Postgres.Repositories;
|
||||
|
||||
namespace StellaOps.Evidence.Persistence.Postgres;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL implementation of <see cref="IEvidenceStore"/>.
|
||||
/// Stores evidence records with content-addressed IDs and tenant isolation via RLS.
|
||||
/// </summary>
|
||||
public sealed class PostgresEvidenceStore : RepositoryBase<EvidenceDataSource>, IEvidenceStore
|
||||
{
|
||||
private readonly string _tenantId;
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new PostgreSQL evidence store for the specified tenant.
|
||||
/// </summary>
|
||||
/// <param name="dataSource">Evidence data source.</param>
|
||||
/// <param name="tenantId">Tenant identifier for RLS.</param>
|
||||
/// <param name="logger">Logger instance.</param>
|
||||
public PostgresEvidenceStore(
|
||||
EvidenceDataSource dataSource,
|
||||
string tenantId,
|
||||
ILogger<PostgresEvidenceStore> logger)
|
||||
: base(dataSource, logger)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
|
||||
_tenantId = tenantId;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<string> StoreAsync(IEvidence evidence, CancellationToken ct = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(evidence);
|
||||
|
||||
const string sql = """
|
||||
INSERT INTO evidence.records (
|
||||
evidence_id, subject_node_id, evidence_type, payload,
|
||||
payload_schema_ver, external_cid, provenance, signatures, tenant_id
|
||||
) VALUES (
|
||||
@evidenceId, @subjectNodeId, @evidenceType, @payload,
|
||||
@payloadSchemaVer, @externalCid, @provenance, @signatures, @tenantId
|
||||
)
|
||||
ON CONFLICT (evidence_id) DO NOTHING
|
||||
RETURNING evidence_id
|
||||
""";
|
||||
|
||||
await using var connection = await DataSource.OpenConnectionAsync(_tenantId, "writer", ct)
|
||||
.ConfigureAwait(false);
|
||||
await using var command = CreateCommand(sql, connection);
|
||||
|
||||
AddEvidenceParameters(command, evidence);
|
||||
|
||||
var result = await command.ExecuteScalarAsync(ct).ConfigureAwait(false);
|
||||
|
||||
// If result is null, row already existed (idempotent)
|
||||
return evidence.EvidenceId;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> StoreBatchAsync(IEnumerable<IEvidence> evidenceRecords, CancellationToken ct = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(evidenceRecords);
|
||||
|
||||
var records = evidenceRecords.ToList();
|
||||
if (records.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
await using var connection = await DataSource.OpenConnectionAsync(_tenantId, "writer", ct)
|
||||
.ConfigureAwait(false);
|
||||
await using var transaction = await connection.BeginTransactionAsync(ct).ConfigureAwait(false);
|
||||
|
||||
var storedCount = 0;
|
||||
|
||||
foreach (var evidence in records)
|
||||
{
|
||||
const string sql = """
|
||||
INSERT INTO evidence.records (
|
||||
evidence_id, subject_node_id, evidence_type, payload,
|
||||
payload_schema_ver, external_cid, provenance, signatures, tenant_id
|
||||
) VALUES (
|
||||
@evidenceId, @subjectNodeId, @evidenceType, @payload,
|
||||
@payloadSchemaVer, @externalCid, @provenance, @signatures, @tenantId
|
||||
)
|
||||
ON CONFLICT (evidence_id) DO NOTHING
|
||||
""";
|
||||
|
||||
await using var command = new NpgsqlCommand(sql, connection, transaction)
|
||||
{
|
||||
CommandTimeout = CommandTimeoutSeconds
|
||||
};
|
||||
|
||||
AddEvidenceParameters(command, evidence);
|
||||
|
||||
var affected = await command.ExecuteNonQueryAsync(ct).ConfigureAwait(false);
|
||||
if (affected > 0)
|
||||
{
|
||||
storedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
await transaction.CommitAsync(ct).ConfigureAwait(false);
|
||||
return storedCount;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IEvidence?> GetByIdAsync(string evidenceId, CancellationToken ct = default)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(evidenceId);
|
||||
|
||||
const string sql = """
|
||||
SELECT evidence_id, subject_node_id, evidence_type, payload,
|
||||
payload_schema_ver, external_cid, provenance, signatures
|
||||
FROM evidence.records
|
||||
WHERE evidence_id = @evidenceId
|
||||
""";
|
||||
|
||||
return await QuerySingleOrDefaultAsync<IEvidence>(
|
||||
_tenantId,
|
||||
sql,
|
||||
cmd => AddParameter(cmd, "@evidenceId", evidenceId),
|
||||
MapEvidence,
|
||||
ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<IEvidence>> GetBySubjectAsync(
|
||||
string subjectNodeId,
|
||||
EvidenceType? typeFilter = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(subjectNodeId);
|
||||
|
||||
var sql = """
|
||||
SELECT evidence_id, subject_node_id, evidence_type, payload,
|
||||
payload_schema_ver, external_cid, provenance, signatures
|
||||
FROM evidence.records
|
||||
WHERE subject_node_id = @subjectNodeId
|
||||
""";
|
||||
|
||||
if (typeFilter.HasValue)
|
||||
{
|
||||
sql += " AND evidence_type = @evidenceType";
|
||||
}
|
||||
|
||||
sql += " ORDER BY created_at DESC";
|
||||
|
||||
return await QueryAsync<IEvidence>(
|
||||
_tenantId,
|
||||
sql,
|
||||
cmd =>
|
||||
{
|
||||
AddParameter(cmd, "@subjectNodeId", subjectNodeId);
|
||||
if (typeFilter.HasValue)
|
||||
{
|
||||
AddParameter(cmd, "@evidenceType", (short)typeFilter.Value);
|
||||
}
|
||||
},
|
||||
MapEvidence,
|
||||
ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<IEvidence>> GetByTypeAsync(
|
||||
EvidenceType evidenceType,
|
||||
int limit = 100,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
const string sql = """
|
||||
SELECT evidence_id, subject_node_id, evidence_type, payload,
|
||||
payload_schema_ver, external_cid, provenance, signatures
|
||||
FROM evidence.records
|
||||
WHERE evidence_type = @evidenceType
|
||||
ORDER BY created_at DESC
|
||||
LIMIT @limit
|
||||
""";
|
||||
|
||||
return await QueryAsync<IEvidence>(
|
||||
_tenantId,
|
||||
sql,
|
||||
cmd =>
|
||||
{
|
||||
AddParameter(cmd, "@evidenceType", (short)evidenceType);
|
||||
AddParameter(cmd, "@limit", limit);
|
||||
},
|
||||
MapEvidence,
|
||||
ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> ExistsAsync(string subjectNodeId, EvidenceType type, CancellationToken ct = default)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(subjectNodeId);
|
||||
|
||||
const string sql = """
|
||||
SELECT EXISTS(
|
||||
SELECT 1 FROM evidence.records
|
||||
WHERE subject_node_id = @subjectNodeId
|
||||
AND evidence_type = @evidenceType
|
||||
)
|
||||
""";
|
||||
|
||||
var result = await ExecuteScalarAsync<bool>(
|
||||
_tenantId,
|
||||
sql,
|
||||
cmd =>
|
||||
{
|
||||
AddParameter(cmd, "@subjectNodeId", subjectNodeId);
|
||||
AddParameter(cmd, "@evidenceType", (short)type);
|
||||
},
|
||||
ct).ConfigureAwait(false);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> DeleteAsync(string evidenceId, CancellationToken ct = default)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(evidenceId);
|
||||
|
||||
const string sql = """
|
||||
DELETE FROM evidence.records
|
||||
WHERE evidence_id = @evidenceId
|
||||
""";
|
||||
|
||||
var affected = await ExecuteAsync(
|
||||
_tenantId,
|
||||
sql,
|
||||
cmd => AddParameter(cmd, "@evidenceId", evidenceId),
|
||||
ct).ConfigureAwait(false);
|
||||
|
||||
return affected > 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> CountBySubjectAsync(string subjectNodeId, CancellationToken ct = default)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(subjectNodeId);
|
||||
|
||||
const string sql = """
|
||||
SELECT COUNT(*)
|
||||
FROM evidence.records
|
||||
WHERE subject_node_id = @subjectNodeId
|
||||
""";
|
||||
|
||||
var result = await ExecuteScalarAsync<long>(
|
||||
_tenantId,
|
||||
sql,
|
||||
cmd => AddParameter(cmd, "@subjectNodeId", subjectNodeId),
|
||||
ct).ConfigureAwait(false);
|
||||
|
||||
return (int)result;
|
||||
}
|
||||
|
||||
private void AddEvidenceParameters(NpgsqlCommand command, IEvidence evidence)
|
||||
{
|
||||
AddParameter(command, "@evidenceId", evidence.EvidenceId);
|
||||
AddParameter(command, "@subjectNodeId", evidence.SubjectNodeId);
|
||||
AddParameter(command, "@evidenceType", (short)evidence.EvidenceType);
|
||||
command.Parameters.Add(new NpgsqlParameter<byte[]>("@payload", NpgsqlDbType.Bytea)
|
||||
{
|
||||
TypedValue = evidence.Payload.ToArray()
|
||||
});
|
||||
AddParameter(command, "@payloadSchemaVer", evidence.PayloadSchemaVersion);
|
||||
AddParameter(command, "@externalCid", evidence.ExternalPayloadCid);
|
||||
AddJsonbParameter(command, "@provenance", JsonSerializer.Serialize(evidence.Provenance, JsonOptions));
|
||||
AddJsonbParameter(command, "@signatures", JsonSerializer.Serialize(evidence.Signatures, JsonOptions));
|
||||
AddParameter(command, "@tenantId", Guid.Parse(_tenantId));
|
||||
}
|
||||
|
||||
private static IEvidence MapEvidence(NpgsqlDataReader reader)
|
||||
{
|
||||
var evidenceId = reader.GetString(0);
|
||||
var subjectNodeId = reader.GetString(1);
|
||||
var evidenceType = (EvidenceType)reader.GetInt16(2);
|
||||
var payload = reader.GetFieldValue<byte[]>(3);
|
||||
var payloadSchemaVer = reader.GetString(4);
|
||||
var externalCid = GetNullableString(reader, 5);
|
||||
var provenanceJson = reader.GetString(6);
|
||||
var signaturesJson = reader.GetString(7);
|
||||
|
||||
var provenance = JsonSerializer.Deserialize<EvidenceProvenance>(provenanceJson, JsonOptions)
|
||||
?? throw new InvalidOperationException($"Failed to deserialize provenance for evidence {evidenceId}");
|
||||
|
||||
var signatures = JsonSerializer.Deserialize<List<EvidenceSignature>>(signaturesJson, JsonOptions)
|
||||
?? [];
|
||||
|
||||
return new EvidenceRecord
|
||||
{
|
||||
EvidenceId = evidenceId,
|
||||
SubjectNodeId = subjectNodeId,
|
||||
EvidenceType = evidenceType,
|
||||
Payload = payload,
|
||||
PayloadSchemaVersion = payloadSchemaVer,
|
||||
ExternalPayloadCid = externalCid,
|
||||
Provenance = provenance,
|
||||
Signatures = signatures
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Evidence.Core;
|
||||
|
||||
namespace StellaOps.Evidence.Persistence.Postgres;
|
||||
|
||||
/// <summary>
|
||||
/// Factory for creating tenant-scoped PostgreSQL evidence stores.
|
||||
/// </summary>
|
||||
public sealed class PostgresEvidenceStoreFactory
|
||||
{
|
||||
private readonly EvidenceDataSource _dataSource;
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new evidence store factory.
|
||||
/// </summary>
|
||||
/// <param name="dataSource">Evidence data source.</param>
|
||||
/// <param name="loggerFactory">Logger factory.</param>
|
||||
public PostgresEvidenceStoreFactory(
|
||||
EvidenceDataSource dataSource,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
_dataSource = dataSource ?? throw new ArgumentNullException(nameof(dataSource));
|
||||
_loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an evidence store for the specified tenant.
|
||||
/// </summary>
|
||||
/// <param name="tenantId">Tenant identifier (GUID as string).</param>
|
||||
/// <returns>Evidence store scoped to the tenant.</returns>
|
||||
public IEvidenceStore Create(string tenantId)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
|
||||
|
||||
return new PostgresEvidenceStore(
|
||||
_dataSource,
|
||||
tenantId,
|
||||
_loggerFactory.CreateLogger<PostgresEvidenceStore>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<RootNamespace>StellaOps.Evidence.Persistence</RootNamespace>
|
||||
<AssemblyName>StellaOps.Evidence.Persistence</AssemblyName>
|
||||
<Description>Consolidated persistence layer for StellaOps Evidence module</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Migrations\**\*.sql" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" PrivateAssets="all" />
|
||||
<PackageReference Include="Npgsql" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StellaOps.Evidence.Core\StellaOps.Evidence.Core.csproj" />
|
||||
<ProjectReference Include="..\StellaOps.Infrastructure.Postgres\StellaOps.Infrastructure.Postgres.csproj" />
|
||||
<ProjectReference Include="..\StellaOps.Infrastructure.EfCore\StellaOps.Infrastructure.EfCore.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user