chore: remove kbx-foundation-v36 reference (superseded by v4 implementation)

Removed entire kbx-foundation-v36 directory as it's been replaced by
the new KBX Foundation v4 patterns implemented in this session:
- Registry-driven screen definitions
- Density-aware UI adapter components
- Feature module templates (ShadowRun, Models)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-08-12 01:39:58 +09:00
parent 831c4b467d
commit c41e5063b7
1079 changed files with 13094 additions and 1134 deletions
@@ -0,0 +1,23 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace Kbx.Shared.Runtime;
public sealed class KbxCorrelationMiddleware(RequestDelegate next, ILogger<KbxCorrelationMiddleware> logger)
{
public async Task InvokeAsync(HttpContext context)
{
var correlationId = context.Request.Headers["X-Correlation-Id"].FirstOrDefault();
if (string.IsNullOrWhiteSpace(correlationId)) correlationId = Guid.NewGuid().ToString("N");
context.TraceIdentifier = correlationId;
context.Response.Headers["X-Correlation-Id"] = correlationId;
Activity.Current?.SetTag("kbx.correlation.id", correlationId);
using (logger.BeginScope(new Dictionary<string, object?> { ["CorrelationId"] = correlationId }))
{
await next(context);
}
}
}
@@ -0,0 +1,31 @@
using Dapper;
using Npgsql;
namespace Kbx.Shared.Runtime;
public sealed class KbxNotificationRepository(NpgsqlDataSource dataSource)
{
public async Task<IReadOnlyList<KbxUserNotificationDto>> GetRecentAsync(Guid tenantId, Guid userId, int limit, CancellationToken ct)
{
const string sql = """
select id, severity, title, message, created_at as CreatedAt, read_at as ReadAt,
screen_id as ScreenId, entity_type as EntityType, entity_id as EntityId,
action::text as ActionJson
from kbx.user_notifications
where tenant_id=@tenantId and user_id=@userId
and (expires_at is null or expires_at > now())
order by created_at desc
limit @limit;
""";
await using var connection = await dataSource.OpenConnectionAsync(ct);
var rows = await connection.QueryAsync<KbxUserNotificationDto>(new CommandDefinition(sql, new { tenantId, userId, limit = Math.Clamp(limit, 1, 100) }, cancellationToken: ct));
return rows.AsList();
}
public async Task MarkReadAsync(Guid tenantId, Guid userId, Guid id, CancellationToken ct)
{
const string sql = "update kbx.user_notifications set read_at=coalesce(read_at, now()) where tenant_id=@tenantId and user_id=@userId and id=@id";
await using var connection = await dataSource.OpenConnectionAsync(ct);
await connection.ExecuteAsync(new CommandDefinition(sql, new { tenantId, userId, id }, cancellationToken: ct));
}
}
@@ -0,0 +1,14 @@
namespace Kbx.Shared.Runtime;
public static class KbxOperationPolicy
{
public static bool IsTerminal(string status) => status is "completed" or "partially-completed" or "failed" or "cancelled";
public static bool CanRetryTransport(string httpMethod, bool hasIdempotencyKey)
{
if (httpMethod is "GET" or "HEAD") return true;
return hasIdempotencyKey;
}
public static bool CanStartCommit(string currentStatus) => currentStatus is "validated" or "retryable";
}
@@ -0,0 +1,24 @@
using Dapper;
using Npgsql;
namespace Kbx.Shared.Runtime;
public sealed class KbxOperationRunRepository(NpgsqlDataSource dataSource)
{
public async Task<IReadOnlyList<KbxOperationRunDto>> GetRecentAsync(Guid tenantId, Guid userId, int limit, CancellationToken ct)
{
const string sql = """
select id, operation_type as Type, title, source_screen_id as SourceScreenId,
status, requested_at as RequestedAt, started_at as StartedAt,
completed_at as CompletedAt, processed, total, succeeded, failed,
correlation_id as CorrelationId, result_message as ResultMessage
from kbx.operation_runs
where tenant_id=@tenantId and user_id=@userId
order by requested_at desc
limit @limit;
""";
await using var connection = await dataSource.OpenConnectionAsync(ct);
var rows = await connection.QueryAsync<KbxOperationRunDto>(new CommandDefinition(sql, new { tenantId, userId, limit = Math.Clamp(limit, 1, 50) }, cancellationToken: ct));
return rows.AsList();
}
}
@@ -0,0 +1,47 @@
namespace Kbx.Shared.Runtime;
public enum KbxRuntimeMode { Normal, Degraded, ReadOnly, Offline }
public enum KbxOperationStatus { Queued, Running, Completed, PartiallyCompleted, Failed, Cancelled }
public sealed record KbxRequestContext(
Guid? TenantId,
Guid? UserId,
string? ScreenId,
string CorrelationId,
string? RequestId);
public sealed record KbxRuntimeNotice(
KbxRuntimeMode Mode,
string Title,
string? Message,
DateTimeOffset? Since,
string? CorrelationId,
bool RetryAllowed);
public sealed record KbxOperationRunDto(
Guid Id,
string Type,
string Title,
string? SourceScreenId,
string Status,
DateTimeOffset RequestedAt,
DateTimeOffset? StartedAt,
DateTimeOffset? CompletedAt,
long Processed,
long? Total,
long Succeeded,
long Failed,
string? CorrelationId,
string? ResultMessage);
public sealed record KbxUserNotificationDto(
Guid Id,
string Severity,
string Title,
string? Message,
DateTimeOffset CreatedAt,
DateTimeOffset? ReadAt,
string? ScreenId,
string? EntityType,
string? EntityId,
string? ActionJson);
@@ -0,0 +1,32 @@
using Dapper;
using Npgsql;
namespace Kbx.Shared.Runtime;
public sealed class KbxRuntimeNoticeRepository(NpgsqlDataSource dataSource)
{
public async Task<KbxRuntimeNotice?> GetActiveAsync(Guid tenantId, CancellationToken ct)
{
const string sql = """
select mode as Mode, title, message, started_at as Since,
correlation_id as CorrelationId
from kbx.runtime_incidents
where ended_at is null
and (tenant_id is null or tenant_id=@tenantId)
order by case mode when 'read-only' then 0 when 'offline' then 1 else 2 end,
started_at desc
limit 1;
""";
await using var connection = await dataSource.OpenConnectionAsync(ct);
var row = await connection.QuerySingleOrDefaultAsync<RuntimeNoticeRow>(new CommandDefinition(sql, new { tenantId }, cancellationToken: ct));
if (row is null) return null;
var mode = row.Mode switch {
"read-only" => KbxRuntimeMode.ReadOnly,
"offline" => KbxRuntimeMode.Offline,
_ => KbxRuntimeMode.Degraded,
};
return new KbxRuntimeNotice(mode, row.Title, row.Message, row.Since, row.CorrelationId, mode is KbxRuntimeMode.Degraded or KbxRuntimeMode.Offline);
}
private sealed record RuntimeNoticeRow(string Mode, string Title, string? Message, DateTimeOffset Since, string? CorrelationId);
}
@@ -0,0 +1,14 @@
using Microsoft.Extensions.DependencyInjection;
namespace Kbx.Shared.Runtime;
public static class KbxRuntimeRegistration
{
public static IServiceCollection AddKbxRuntime(this IServiceCollection services)
{
services.AddScoped<KbxOperationRunRepository>();
services.AddScoped<KbxNotificationRepository>();
services.AddScoped<KbxRuntimeNoticeRepository>();
return services;
}
}
@@ -0,0 +1,37 @@
using System.Diagnostics;
using Microsoft.Extensions.Logging;
namespace Kbx.Shared.Runtime;
public static class KbxTelemetry
{
public static readonly ActivitySource ActivitySource = new("KBX.BusinessRuntime");
public static Activity? StartOperation(
string operationName,
KbxRequestContext context,
string? entityType = null,
string? entityId = null)
{
var activity = ActivitySource.StartActivity(operationName, ActivityKind.Internal);
if (activity is null) return null;
activity.SetTag("kbx.screen.id", context.ScreenId);
activity.SetTag("kbx.correlation.id", context.CorrelationId);
activity.SetTag("kbx.tenant.id", context.TenantId?.ToString());
activity.SetTag("kbx.entity.type", entityType);
activity.SetTag("kbx.entity.id", entityId);
return activity;
}
public static void LogFailure(
ILogger logger,
Exception exception,
KbxRequestContext context,
string operation)
{
logger.LogError(exception,
"KBX operation failed. Operation={Operation} ScreenId={ScreenId} CorrelationId={CorrelationId}",
operation, context.ScreenId, context.CorrelationId);
}
}
@@ -0,0 +1,11 @@
using System.Security.Claims;
namespace Kbx.Shared.Runtime;
public static class RuntimeIdentity
{
public static Guid TenantId(ClaimsPrincipal user) => Parse(user.FindFirst("tenant_id")?.Value ?? user.FindFirst("tenant")?.Value);
public static Guid UserId(ClaimsPrincipal user) => Parse(user.FindFirst(ClaimTypes.NameIdentifier)?.Value);
private static Guid Parse(string? value) => Guid.TryParse(value, out var id) ? id : Guid.Empty;
}
@@ -0,0 +1,16 @@
using Kbx.Shared.Runtime;
using Xunit;
public sealed class KbxOperationPolicyTests
{
[Theory]
[InlineData("GET", false, true)]
[InlineData("POST", false, false)]
[InlineData("POST", true, true)]
public void Mutation_retry_requires_idempotency(string method, bool key, bool expected)
=> Assert.Equal(expected, KbxOperationPolicy.CanRetryTransport(method, key));
[Fact]
public void Terminal_operation_is_not_treated_as_running()
=> Assert.True(KbxOperationPolicy.IsTerminal("partially-completed"));
}