Initial commit: Add project files
ci / backend (push) Failing after 12s
ci / frontend (push) Failing after 19s
ci / static (push) Failing after 45s

This commit is contained in:
2026-08-02 05:15:36 +09:00
commit dcd1322d41
636 changed files with 122352 additions and 0 deletions
@@ -0,0 +1,10 @@
namespace __MODULE__;
public static class Module
{
public static IServiceCollection Add__MODULE__(this IServiceCollection services)
{
// Register only module-owned policies, application handlers, and adapters.
return services;
}
}
@@ -0,0 +1,19 @@
using FastEndpoints;
namespace __MODULE__.Features.__SLICE__;
public sealed class Endpoint(Handler handler) : Endpoint<Request, Response>
{
public override void Configure()
{
__HTTP_METHOD__("/__ROUTE__");
Roles("__ROLE__");
Description(x => x.WithTags("__MODULE__"));
}
public override async Task HandleAsync(Request req, CancellationToken ct)
{
var result = await handler.HandleAsync(req, HttpContext.TraceIdentifier, ct);
await Send.OkAsync(result, ct);
}
}
@@ -0,0 +1,10 @@
namespace __MODULE__.Features.__SLICE__;
public sealed class Handler(/* narrow ports only */)
{
public async Task<Response> HandleAsync(Request request, string correlationId, CancellationToken ct)
{
// 1) server-side PIT context 2) idempotency 3) transaction 4) policy 5) outbox
throw new NotImplementedException("Complete the approved Slice Spec; do not guess missing contracts.");
}
}
@@ -0,0 +1,10 @@
namespace __MODULE__.Domain;
public sealed class __POLICY__
{
public Decision Evaluate(ApprovedPointInTimeInput input)
{
// Pure decision only. No Dapper, HTTP, Hangfire, DateTime.Now, or mutable global state.
throw new NotImplementedException();
}
}
@@ -0,0 +1,7 @@
# __SLICE__
- Requirement: __REQUIREMENT_ID__
- Module: __MODULE__
- Route: `__HTTP_METHOD__ /__ROUTE__`
- Role: `__ROLE__`
- Generated status: SCAFFOLD_ONLY / DECISION_REQUIRED contracts remain blocked.
@@ -0,0 +1,3 @@
namespace __MODULE__.Features.__SLICE__;
public sealed record Request(/* approved request fields only */);
@@ -0,0 +1,3 @@
namespace __MODULE__.Features.__SLICE__;
public sealed record Response(/* approved response fields only */);
@@ -0,0 +1,11 @@
namespace __MODULE__.Features.__SLICE__;
internal static class Sql
{
internal const string ReadApprovedContext = """
-- SCAFFOLD_ONLY: replace only after schema ownership, explicit columns,
-- PIT cutoff, quality gate and key contract are approved in the Slice Spec.
select 1 as contract_not_ready
where false;
""";
}
@@ -0,0 +1,11 @@
public sealed class __SLICE__Tests
{
[Fact]
public void Golden_case_is_reproducible() { }
[Fact]
public void Boundary_and_forbidden_transition_fail_closed() { }
[Fact]
public void Same_idempotency_key_has_one_side_effect() { }
}
@@ -0,0 +1,12 @@
using FastEndpoints;
using FluentValidation;
namespace __MODULE__.Features.__SLICE__;
public sealed class Validator : Validator<Request>
{
public Validator()
{
// Translate approved boundary rules only; UNKNOWN and DECISION_REQUIRED must fail closed.
}
}
@@ -0,0 +1,13 @@
using Hangfire;
public sealed class __JOB__(IJobRunRepository runs, IClock clock)
{
[Queue("__QUEUE__")]
[AutomaticRetry(Attempts = 0)] // retry policy is explicit and evidence-driven
public async Task ExecuteAsync(JobCommand command, CancellationToken ct)
{
// TryStart(idempotency/scope/version/hash) → heartbeat → work → Complete.
// No current-value overwrite; reprocess creates a revision.
throw new NotImplementedException();
}
}
+12
View File
@@ -0,0 +1,12 @@
-- Immutable ordered DbUp migration. Never rewrite after release.
create schema if not exists __schema__;
create table if not exists __schema__.__table__ (
id uuid primary key,
source_id text not null,
published_at timestamptz not null,
ingested_at timestamptz not null,
revision_no integer not null,
content_hash text not null,
unique (source_id, revision_no)
);
@@ -0,0 +1,8 @@
using FastEndpoints;
namespace KArtSell.Modules.__MODULE__.Features.__FEATURE__;
public sealed class Endpoint(IHandler handler) : Endpoint<Request, Response>
{
public override void Configure() { Post("/api/v1/__MODULE__/__FEATURE__"); Policies("__SLICE_ID__"); }
public override async Task HandleAsync(Request request, CancellationToken ct)
=> await SendOkAsync(await handler.HandleAsync(request, ct), ct);
}
@@ -0,0 +1,7 @@
namespace KArtSell.Modules.__MODULE__.Features.__FEATURE__;
public interface IHandler { Task<Response> HandleAsync(Request request, CancellationToken ct); }
public sealed class Handler : IHandler
{
public Task<Response> HandleAsync(Request request, CancellationToken ct)
=> throw new NotImplementedException("Load approved server-side PIT context, execute a pure policy, persist decision and Outbox in one transaction.");
}
@@ -0,0 +1,2 @@
# __SLICE_ID__ __MODULE__.__FEATURE__
Required before implementation: Requirement/Policy/Data/API/DB/Job/UI/Test IDs, PIT cutoff, state transition, idempotency key, transaction boundary, Outbox event, Golden/boundary/failure/replay tests, metric/alert/runbook/rollback.
@@ -0,0 +1,2 @@
namespace KArtSell.Modules.__MODULE__.Features.__FEATURE__;
public sealed record Request(string IdempotencyKey, string ScopeId, DateTimeOffset AsOf);
@@ -0,0 +1,2 @@
namespace KArtSell.Modules.__MODULE__.Features.__FEATURE__;
public sealed record Response(Guid Id, string Status, string EvidenceHash, DateTimeOffset AsOf);
+5
View File
@@ -0,0 +1,5 @@
namespace KArtSell.Modules.__MODULE__.Features.__FEATURE__;
internal static class Sql
{
public const string LoadContext = """select /* explicit columns */ 1 where @as_of is not null;""";
}
@@ -0,0 +1,6 @@
using FluentValidation;
namespace KArtSell.Modules.__MODULE__.Features.__FEATURE__;
public sealed class Validator : AbstractValidator<Request>
{
public Validator() { RuleFor(x => x.IdempotencyKey).NotEmpty().MaximumLength(100); RuleFor(x => x.ScopeId).NotEmpty(); RuleFor(x => x.AsOf).NotEmpty(); }
}
+6
View File
@@ -0,0 +1,6 @@
import { api } from '@/shared/api/client'
import { responseSchema, type RequestDto, type ResponseDto } from './schema'
export async function execute(request: RequestDto): Promise<ResponseDto> {
const response = await api.post('/api/v1/__MODULE__/__FEATURE__', request, { headers: { 'Idempotency-Key': request.idempotencyKey } })
return responseSchema.parse(response.data)
}
@@ -0,0 +1,2 @@
<script setup lang="ts">import { CrudDetailPage } from '@/shared/ui/screen-types'</script>
<template><CrudDetailPage title="__FEATURE__" status="IMPLEMENTATION_REQUIRED"><p>__SLICE_ID__ Slice packet. Replace this placeholder only after the Slice Ready Gate is approved.</p></CrudDetailPage></template>
@@ -0,0 +1,4 @@
import { useMutation, useQueryClient } from '@tanstack/vue-query'
import { execute } from './api'
export const queryKeys = { root: ['__MODULE__','__FEATURE__'] as const }
export function useExecuteMutation() { const client=useQueryClient(); return useMutation({ mutationFn: execute, onSuccess: async()=>client.invalidateQueries({queryKey:queryKeys.root}) }) }
@@ -0,0 +1,5 @@
import { z } from 'zod'
export const requestSchema = z.object({ idempotencyKey: z.string().min(1).max(100), scopeId: z.string().min(1), asOf: z.string().datetime() })
export const responseSchema = z.object({ id: z.string().uuid(), status: z.string(), evidenceHash: z.string().min(16), asOf: z.string().datetime() })
export type RequestDto = z.infer<typeof requestSchema>
export type ResponseDto = z.infer<typeof responseSchema>
@@ -0,0 +1,3 @@
import { describe, expect, it } from 'vitest'
import { requestSchema } from '../schema'
describe('__SLICE_ID__ request contract',()=>{it('rejects missing idempotency evidence',()=>{expect(requestSchema.safeParse({scopeId:'x',asOf:new Date().toISOString()}).success).toBe(false)})})
+8
View File
@@ -0,0 +1,8 @@
import { api } from '@/shared/api/client'
import { requestSchema, responseSchema, type Request } from './schema'
export async function execute(request: Request) {
const body = requestSchema.parse(request)
const response = await api.post('/__ROUTE__', body)
return responseSchema.parse(response.data)
}
+10
View File
@@ -0,0 +1,10 @@
<script setup lang="ts">
// Server state belongs to TanStack Query. Pinia is limited to session/UI preferences.
</script>
<template>
<main>
<h1>__FEATURE__</h1>
<p>Contract-first scaffold. Complete Zod request/response schemas before enabling the route.</p>
</main>
</template>
@@ -0,0 +1,5 @@
import { useMutation, useQuery } from '@tanstack/vue-query'
import { execute } from './api'
export const featureKeys = { all: ['__FEATURE__'] as const }
export function useExecute() { return useMutation({ mutationFn: execute }) }
@@ -0,0 +1,8 @@
import { describe, expect, it } from 'vitest'
import { responseSchema } from './schema'
describe('__FEATURE__ response contract', () => {
it('fails closed for an unapproved empty payload', () => {
expect(responseSchema.safeParse({}).success).toBe(false)
})
})
+6
View File
@@ -0,0 +1,6 @@
import { z } from 'zod'
export const requestSchema = z.object({ /* approved contract */ })
export const responseSchema = z.object({ /* runtime trust boundary */ })
export type Request = z.infer<typeof requestSchema>
export type Response = z.infer<typeof responseSchema>
@@ -0,0 +1,10 @@
<script setup lang="ts">
import { DetailReadPage, EvidenceVersionSet } from '@/shared/ui'
const versionSet = { datasetId: 'DECISION_REQUIRED', dataHash: 'DECISION_REQUIRED', modelVersion: 'DECISION_REQUIRED', configVersion: 'DECISION_REQUIRED', codeSha: 'DECISION_REQUIRED', contractVersion: '__CONTRACT_VERSION__' }
</script>
<template>
<DetailReadPage title="__TITLE__" state="READY" :evidence="{ version: '__CONTRACT_VERSION__' }">
<section class="ks-card ks-section">상세 내용</section>
<template #evidence><EvidenceVersionSet :value="versionSet" /></template>
</DetailReadPage>
</template>
@@ -0,0 +1,10 @@
<script setup lang="ts">
import { EditFormPage } from '@/shared/ui/screen-types'
import { KsButton, KsTextField } from '@/shared/ui/components'
</script>
<template>
<EditFormPage title="__TITLE__" state="READY" :evidence="{ version: '__CONTRACT_VERSION__' }" @submit="undefined">
<KsTextField model-value="" label="이름" />
<template #footer><KsButton label="저장" type="submit" /></template>
</EditFormPage>
</template>
@@ -0,0 +1,10 @@
<script setup lang="ts">
import { SearchListCrudPage } from '@/shared/ui/screen-types'
import { KsButton, KsDataGrid } from '@/shared/ui/components'
</script>
<template>
<SearchListCrudPage title="__TITLE__" state="LOADING" :evidence="{ version: '__CONTRACT_VERSION__' }">
<template #actions><KsButton label="등록" /></template>
<KsDataGrid :rows="[]" :columns="[]" />
</SearchListCrudPage>
</template>
@@ -0,0 +1,10 @@
<script setup lang="ts">
import { ApprovalWorkbenchPage } from '@/shared/ui/screen-types'
</script>
<template>
<ApprovalWorkbenchPage title="__TITLE__" state="READY" :evidence="{ version: '__CONTRACT_VERSION__' }">
<template #queue>검토 대기열</template>
<template #detail>증거와 상세</template>
<template #decision>승인/보류/기각</template>
</ApprovalWorkbenchPage>
</template>