Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.
This commit is contained in:
638
docs/implplan/SPRINT_1229_002_BE_sbom-sources-triggers.md
Normal file
638
docs/implplan/SPRINT_1229_002_BE_sbom-sources-triggers.md
Normal file
@@ -0,0 +1,638 @@
|
||||
# SPRINT_1229_002_BE: SBOM Sources Trigger Service
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This sprint implements the **trigger service** that dispatches scans based on source configurations. It handles scheduled (cron) triggers, webhook handlers (Zastava registry, Git), manual triggers, and retry logic.
|
||||
|
||||
**Working Directory:** `src/Scanner/`, `src/Scheduler/`
|
||||
**Module:** BE (Backend)
|
||||
**Dependencies:** SPRINT_1229_001_BE (Sources Foundation)
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Source Trigger Service │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Trigger Dispatcher │ │
|
||||
│ │ │ │
|
||||
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
||||
│ │ │Schedule │ │ Webhook │ │ Manual │ │ Retry │ │ │
|
||||
│ │ │ (Cron) │ │ Handler │ │ Trigger │ │ Handler │ │ │
|
||||
│ │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ └──────────────┴──────────────┴──────────────┘ │ │
|
||||
│ │ │ │ │
|
||||
│ │ ┌─────────▼─────────┐ │ │
|
||||
│ │ │ Source Context │ │ │
|
||||
│ │ │ Resolver │ │ │
|
||||
│ │ └─────────┬─────────┘ │ │
|
||||
│ │ │ │ │
|
||||
│ └──────────────────────────────┼────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌──────────────────────────────▼────────────────────────────────────────┐ │
|
||||
│ │ Source Type Handlers │ │
|
||||
│ │ │ │
|
||||
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │ │
|
||||
│ │ │ Zastava │ │ Docker │ │ CLI │ │ Git │ │ │
|
||||
│ │ │ Handler │ │ Handler │ │ Handler │ │ Handler │ │ │
|
||||
│ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └────────┬────────┘ │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ └─────────┼───────────────┼───────────────┼──────────────────┼──────────┘ │
|
||||
│ │ │ │ │ │
|
||||
└────────────┼───────────────┼───────────────┼──────────────────┼────────────┘
|
||||
│ │ │ │
|
||||
▼ ▼ ▼ ▼
|
||||
┌───────────────────────────────────────────────────────────────────┐
|
||||
│ Scanner Job Queue │
|
||||
│ │
|
||||
│ ScanJob { imageRef, sourceId, correlationId, metadata } │
|
||||
│ │
|
||||
└───────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component Design
|
||||
|
||||
### 1. Trigger Dispatcher
|
||||
|
||||
Centralized coordinator for all trigger types:
|
||||
|
||||
```csharp
|
||||
public interface ISourceTriggerDispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Dispatch a trigger for a source, creating scan jobs as appropriate.
|
||||
/// </summary>
|
||||
Task<SbomSourceRun> DispatchAsync(
|
||||
Guid sourceId,
|
||||
SbomSourceRunTrigger trigger,
|
||||
string? triggerDetails = null,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Process scheduled sources that are due.
|
||||
/// Called by scheduler worker.
|
||||
/// </summary>
|
||||
Task ProcessScheduledSourcesAsync(CancellationToken ct);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Source Type Handlers
|
||||
|
||||
Each source type has a dedicated handler:
|
||||
|
||||
```csharp
|
||||
public interface ISourceTypeHandler
|
||||
{
|
||||
SbomSourceType SourceType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Discover items to scan based on source configuration.
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<ScanTarget>> DiscoverTargetsAsync(
|
||||
SbomSource source,
|
||||
TriggerContext context,
|
||||
CancellationToken ct);
|
||||
|
||||
/// <summary>
|
||||
/// Validate source configuration.
|
||||
/// </summary>
|
||||
ValidationResult ValidateConfiguration(JsonDocument configuration);
|
||||
|
||||
/// <summary>
|
||||
/// Test source connection with credentials.
|
||||
/// </summary>
|
||||
Task<ConnectionTestResult> TestConnectionAsync(
|
||||
SbomSource source,
|
||||
CancellationToken ct);
|
||||
}
|
||||
|
||||
public record ScanTarget(
|
||||
string Reference, // Image ref, repo URL, etc.
|
||||
string? Digest, // Optional pinned digest
|
||||
Dictionary<string, string> Metadata
|
||||
);
|
||||
|
||||
public record TriggerContext(
|
||||
SbomSourceRunTrigger Trigger,
|
||||
string? TriggerDetails,
|
||||
string CorrelationId,
|
||||
JsonDocument? WebhookPayload
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Webhook Handlers
|
||||
|
||||
### Zastava Registry Webhook
|
||||
|
||||
**Endpoint:** `POST /api/v1/webhooks/zastava/{sourceId}`
|
||||
|
||||
**Supported Registry Types:**
|
||||
- Docker Hub
|
||||
- Harbor
|
||||
- Quay
|
||||
- AWS ECR
|
||||
- Google GCR
|
||||
- Azure ACR
|
||||
- GitHub Container Registry
|
||||
- Generic (configurable payload mapping)
|
||||
|
||||
```csharp
|
||||
public class ZastavaWebhookHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle registry push webhook.
|
||||
/// </summary>
|
||||
public async Task<WebhookResult> HandleAsync(
|
||||
Guid sourceId,
|
||||
HttpRequest request,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// 1. Verify webhook signature
|
||||
var source = await _sourceRepo.GetAsync(sourceId, ct);
|
||||
if (!VerifySignature(request, source.WebhookSecretRef))
|
||||
return WebhookResult.Unauthorized();
|
||||
|
||||
// 2. Parse payload based on registry type
|
||||
var config = source.GetConfiguration<ZastavaSourceConfig>();
|
||||
var payload = await ParsePayload(request, config.RegistryType);
|
||||
|
||||
// 3. Check filters (repo patterns, tag patterns)
|
||||
if (!MatchesFilters(payload, config.Filters))
|
||||
return WebhookResult.Skipped("Does not match filters");
|
||||
|
||||
// 4. Dispatch trigger
|
||||
var run = await _dispatcher.DispatchAsync(
|
||||
sourceId,
|
||||
SbomSourceRunTrigger.Webhook,
|
||||
$"push:{payload.Repository}:{payload.Tag}");
|
||||
|
||||
return WebhookResult.Accepted(run.RunId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Payload Normalization:**
|
||||
|
||||
```csharp
|
||||
public record RegistryPushPayload(
|
||||
string Repository,
|
||||
string Tag,
|
||||
string? Digest,
|
||||
string? PushedBy,
|
||||
DateTimeOffset Timestamp,
|
||||
Dictionary<string, string> RawHeaders
|
||||
);
|
||||
|
||||
public interface IRegistryPayloadParser
|
||||
{
|
||||
RegistryPushPayload Parse(HttpRequest request);
|
||||
}
|
||||
|
||||
// Implementations:
|
||||
// - DockerHubPayloadParser
|
||||
// - HarborPayloadParser
|
||||
// - QuayPayloadParser
|
||||
// - EcrPayloadParser
|
||||
// - GcrPayloadParser
|
||||
// - AcrPayloadParser
|
||||
// - GhcrPayloadParser
|
||||
// - GenericPayloadParser (JSONPath-based configuration)
|
||||
```
|
||||
|
||||
### Git Webhook
|
||||
|
||||
**Endpoint:** `POST /api/v1/webhooks/git/{sourceId}`
|
||||
|
||||
**Supported Providers:**
|
||||
- GitHub
|
||||
- GitLab
|
||||
- Bitbucket
|
||||
- Azure DevOps
|
||||
- Gitea
|
||||
|
||||
```csharp
|
||||
public class GitWebhookHandler
|
||||
{
|
||||
public async Task<WebhookResult> HandleAsync(
|
||||
Guid sourceId,
|
||||
HttpRequest request,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var source = await _sourceRepo.GetAsync(sourceId, ct);
|
||||
var config = source.GetConfiguration<GitSourceConfig>();
|
||||
|
||||
// 1. Verify webhook signature
|
||||
if (!VerifySignature(request, config.Provider, source.WebhookSecretRef))
|
||||
return WebhookResult.Unauthorized();
|
||||
|
||||
// 2. Parse event type
|
||||
var eventType = DetectEventType(request, config.Provider);
|
||||
|
||||
// 3. Check if event matches triggers
|
||||
if (!ShouldTrigger(eventType, config.Triggers))
|
||||
return WebhookResult.Skipped("Event type not configured for trigger");
|
||||
|
||||
// 4. Parse payload
|
||||
var payload = await ParsePayload(request, config.Provider, eventType);
|
||||
|
||||
// 5. Check branch/tag filters
|
||||
if (!MatchesBranchFilters(payload, config.Branches))
|
||||
return WebhookResult.Skipped("Branch does not match filters");
|
||||
|
||||
// 6. Dispatch
|
||||
var run = await _dispatcher.DispatchAsync(
|
||||
sourceId,
|
||||
SbomSourceRunTrigger.Webhook,
|
||||
$"{eventType}:{payload.Ref}@{payload.CommitSha}");
|
||||
|
||||
return WebhookResult.Accepted(run.RunId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Scheduled Trigger Integration
|
||||
|
||||
### Scheduler Job Type
|
||||
|
||||
Register a new job type with the Scheduler service:
|
||||
|
||||
```csharp
|
||||
public class SourceSchedulerJob : IScheduledJob
|
||||
{
|
||||
public string JobType => "sbom-source-scheduled";
|
||||
|
||||
public async Task ExecuteAsync(JobContext context, CancellationToken ct)
|
||||
{
|
||||
var sourceId = Guid.Parse(context.Payload["sourceId"]);
|
||||
|
||||
await _dispatcher.DispatchAsync(
|
||||
sourceId,
|
||||
SbomSourceRunTrigger.Scheduled,
|
||||
context.Payload["cronExpression"]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Schedule Registration
|
||||
|
||||
When a source with cron schedule is created/updated:
|
||||
|
||||
```csharp
|
||||
public async Task RegisterScheduleAsync(SbomSource source)
|
||||
{
|
||||
if (string.IsNullOrEmpty(source.CronSchedule))
|
||||
return;
|
||||
|
||||
await _schedulerClient.UpsertScheduleAsync(new ScheduleRequest
|
||||
{
|
||||
ScheduleId = $"sbom-source-{source.SourceId}",
|
||||
JobType = "sbom-source-scheduled",
|
||||
Cron = source.CronSchedule,
|
||||
Timezone = source.CronTimezone ?? "UTC",
|
||||
Payload = new Dictionary<string, string>
|
||||
{
|
||||
["sourceId"] = source.SourceId.ToString(),
|
||||
["cronExpression"] = source.CronSchedule
|
||||
},
|
||||
Enabled = source.Status == SbomSourceStatus.Active && !source.Paused
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Source Type Handler Implementations
|
||||
|
||||
### Zastava Handler
|
||||
|
||||
```csharp
|
||||
public class ZastavaSourceHandler : ISourceTypeHandler
|
||||
{
|
||||
public SbomSourceType SourceType => SbomSourceType.Zastava;
|
||||
|
||||
public async Task<IReadOnlyList<ScanTarget>> DiscoverTargetsAsync(
|
||||
SbomSource source,
|
||||
TriggerContext context,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// For webhook triggers, target is in the payload
|
||||
if (context.Trigger == SbomSourceRunTrigger.Webhook &&
|
||||
context.WebhookPayload != null)
|
||||
{
|
||||
var payload = ParseWebhookPayload(context.WebhookPayload);
|
||||
return [new ScanTarget(
|
||||
$"{payload.Repository}:{payload.Tag}",
|
||||
payload.Digest,
|
||||
new() { ["pushedBy"] = payload.PushedBy ?? "unknown" }
|
||||
)];
|
||||
}
|
||||
|
||||
// For scheduled/manual, discover from registry
|
||||
var config = source.GetConfiguration<ZastavaSourceConfig>();
|
||||
var credentials = await _credentialStore.GetAsync(source.AuthRef!);
|
||||
|
||||
var client = _registryClientFactory.Create(config.RegistryType, config.RegistryUrl, credentials);
|
||||
var targets = new List<ScanTarget>();
|
||||
|
||||
foreach (var repoPattern in config.Filters.Repositories)
|
||||
{
|
||||
var repos = await client.ListRepositoriesAsync(repoPattern, ct);
|
||||
foreach (var repo in repos)
|
||||
{
|
||||
var tags = await client.ListTagsAsync(repo, config.Filters.Tags, ct);
|
||||
foreach (var tag in tags)
|
||||
{
|
||||
if (ShouldExclude(repo, tag, config.Filters))
|
||||
continue;
|
||||
|
||||
targets.Add(new ScanTarget($"{repo}:{tag}", null, new()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
public async Task<ConnectionTestResult> TestConnectionAsync(
|
||||
SbomSource source,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var config = source.GetConfiguration<ZastavaSourceConfig>();
|
||||
var credentials = await _credentialStore.GetAsync(source.AuthRef!);
|
||||
|
||||
try
|
||||
{
|
||||
var client = _registryClientFactory.Create(
|
||||
config.RegistryType,
|
||||
config.RegistryUrl,
|
||||
credentials);
|
||||
|
||||
await client.PingAsync(ct);
|
||||
return ConnectionTestResult.Success();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ConnectionTestResult.Failure(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Docker Handler
|
||||
|
||||
```csharp
|
||||
public class DockerSourceHandler : ISourceTypeHandler
|
||||
{
|
||||
public SbomSourceType SourceType => SbomSourceType.Docker;
|
||||
|
||||
public async Task<IReadOnlyList<ScanTarget>> DiscoverTargetsAsync(
|
||||
SbomSource source,
|
||||
TriggerContext context,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var config = source.GetConfiguration<DockerSourceConfig>();
|
||||
var targets = new List<ScanTarget>();
|
||||
|
||||
foreach (var imageSpec in config.Images)
|
||||
{
|
||||
if (imageSpec.TagPatterns?.Any() == true)
|
||||
{
|
||||
// Discover matching tags
|
||||
var credentials = await _credentialStore.GetAsync(source.AuthRef!);
|
||||
var client = _registryClientFactory.Create(config.RegistryUrl, credentials);
|
||||
|
||||
var (repo, _) = ParseImageReference(imageSpec.Reference);
|
||||
var tags = await client.ListTagsAsync(repo, imageSpec.TagPatterns, ct);
|
||||
|
||||
foreach (var tag in tags)
|
||||
{
|
||||
targets.Add(new ScanTarget($"{repo}:{tag}", null, new()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Scan specific reference
|
||||
targets.Add(new ScanTarget(imageSpec.Reference, null, new()));
|
||||
}
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Git Handler
|
||||
|
||||
```csharp
|
||||
public class GitSourceHandler : ISourceTypeHandler
|
||||
{
|
||||
public SbomSourceType SourceType => SbomSourceType.Git;
|
||||
|
||||
public async Task<IReadOnlyList<ScanTarget>> DiscoverTargetsAsync(
|
||||
SbomSource source,
|
||||
TriggerContext context,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var config = source.GetConfiguration<GitSourceConfig>();
|
||||
|
||||
// For webhook triggers, use the payload
|
||||
if (context.Trigger == SbomSourceRunTrigger.Webhook &&
|
||||
context.WebhookPayload != null)
|
||||
{
|
||||
var payload = ParseGitPayload(context.WebhookPayload, config.Provider);
|
||||
return [new ScanTarget(
|
||||
config.RepositoryUrl,
|
||||
null,
|
||||
new()
|
||||
{
|
||||
["ref"] = payload.Ref,
|
||||
["commitSha"] = payload.CommitSha,
|
||||
["branch"] = payload.Branch ?? "",
|
||||
["tag"] = payload.Tag ?? ""
|
||||
}
|
||||
)];
|
||||
}
|
||||
|
||||
// For scheduled/manual, scan default branch or configured branches
|
||||
var credentials = await _credentialStore.GetAsync(source.AuthRef!);
|
||||
var gitClient = _gitClientFactory.Create(config.Provider, credentials);
|
||||
|
||||
var branches = await gitClient.ListBranchesAsync(config.RepositoryUrl, ct);
|
||||
var matchingBranches = branches
|
||||
.Where(b => MatchesBranchPattern(b, config.Branches.Include))
|
||||
.Where(b => !MatchesBranchPattern(b, config.Branches.Exclude ?? []))
|
||||
.ToList();
|
||||
|
||||
return matchingBranches.Select(b => new ScanTarget(
|
||||
config.RepositoryUrl,
|
||||
null,
|
||||
new() { ["branch"] = b, ["ref"] = $"refs/heads/{b}" }
|
||||
)).ToList();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### CLI Handler
|
||||
|
||||
```csharp
|
||||
public class CliSourceHandler : ISourceTypeHandler
|
||||
{
|
||||
public SbomSourceType SourceType => SbomSourceType.Cli;
|
||||
|
||||
public Task<IReadOnlyList<ScanTarget>> DiscoverTargetsAsync(
|
||||
SbomSource source,
|
||||
TriggerContext context,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// CLI sources don't "discover" targets - they receive submissions
|
||||
// This handler validates incoming submissions against source config
|
||||
return Task.FromResult<IReadOnlyList<ScanTarget>>([]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate an incoming CLI submission against source configuration.
|
||||
/// </summary>
|
||||
public ValidationResult ValidateSubmission(
|
||||
SbomSource source,
|
||||
CliSubmissionRequest submission)
|
||||
{
|
||||
var config = source.GetConfiguration<CliSourceConfig>();
|
||||
var errors = new List<string>();
|
||||
|
||||
// Check tool allowlist
|
||||
if (!config.AllowedTools.Contains(submission.Tool))
|
||||
errors.Add($"Tool '{submission.Tool}' not in allowed list");
|
||||
|
||||
// Check CI system
|
||||
if (config.AllowedCiSystems?.Any() == true &&
|
||||
!config.AllowedCiSystems.Contains(submission.CiSystem ?? ""))
|
||||
errors.Add($"CI system '{submission.CiSystem}' not allowed");
|
||||
|
||||
// Check format
|
||||
if (!config.Validation.AllowedFormats.Contains(submission.Format))
|
||||
errors.Add($"Format '{submission.Format}' not allowed");
|
||||
|
||||
// Check size
|
||||
if (submission.SbomSizeBytes > config.Validation.MaxSbomSizeBytes)
|
||||
errors.Add($"SBOM size {submission.SbomSizeBytes} exceeds max {config.Validation.MaxSbomSizeBytes}");
|
||||
|
||||
// Check attribution requirements
|
||||
if (config.Attribution.RequireBuildId && string.IsNullOrEmpty(submission.BuildId))
|
||||
errors.Add("Build ID is required");
|
||||
if (config.Attribution.RequireRepository && string.IsNullOrEmpty(submission.Repository))
|
||||
errors.Add("Repository is required");
|
||||
if (config.Attribution.RequireCommitSha && string.IsNullOrEmpty(submission.CommitSha))
|
||||
errors.Add("Commit SHA is required");
|
||||
|
||||
return errors.Any()
|
||||
? ValidationResult.Failure(errors)
|
||||
: ValidationResult.Success();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task Breakdown
|
||||
|
||||
### T1: Trigger Dispatcher Service (TODO)
|
||||
|
||||
**Files to Create:**
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Sources/Triggers/ISourceTriggerDispatcher.cs`
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Sources/Triggers/SourceTriggerDispatcher.cs`
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Sources/Triggers/TriggerContext.cs`
|
||||
|
||||
### T2: Source Type Handler Interface & Base (TODO)
|
||||
|
||||
**Files to Create:**
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Sources/Handlers/ISourceTypeHandler.cs`
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Sources/Handlers/SourceTypeHandlerBase.cs`
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Sources/Handlers/ScanTarget.cs`
|
||||
|
||||
### T3: Zastava Handler Implementation (TODO)
|
||||
|
||||
**Files to Create:**
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Sources/Handlers/Zastava/ZastavaSourceHandler.cs`
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Sources/Handlers/Zastava/RegistryPayloadParsers.cs`
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Sources/Handlers/Zastava/IRegistryClient.cs`
|
||||
|
||||
### T4: Docker Handler Implementation (TODO)
|
||||
|
||||
**Files to Create:**
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Sources/Handlers/Docker/DockerSourceHandler.cs`
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Sources/Handlers/Docker/ImageDiscovery.cs`
|
||||
|
||||
### T5: Git Handler Implementation (TODO)
|
||||
|
||||
**Files to Create:**
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Sources/Handlers/Git/GitSourceHandler.cs`
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Sources/Handlers/Git/GitPayloadParsers.cs`
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Sources/Handlers/Git/IGitClient.cs`
|
||||
|
||||
### T6: CLI Handler Implementation (TODO)
|
||||
|
||||
**Files to Create:**
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Sources/Handlers/Cli/CliSourceHandler.cs`
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Sources/Handlers/Cli/CliSubmissionValidator.cs`
|
||||
|
||||
### T7: Webhook Endpoints (TODO)
|
||||
|
||||
**Files to Create:**
|
||||
- `src/Scanner/StellaOps.Scanner.WebService/Endpoints/WebhookEndpoints.cs`
|
||||
- `src/Scanner/StellaOps.Scanner.WebService/Webhooks/WebhookSignatureValidator.cs`
|
||||
|
||||
### T8: Scheduler Integration (TODO)
|
||||
|
||||
**Files to Create:**
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Sources/Scheduling/SourceSchedulerJob.cs`
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Sources/Scheduling/ScheduleRegistration.cs`
|
||||
|
||||
### T9: Retry Handler (TODO)
|
||||
|
||||
**Files to Create:**
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Sources/Triggers/RetryHandler.cs`
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Sources/Triggers/RetryPolicy.cs`
|
||||
|
||||
### T10: Unit & Integration Tests (TODO)
|
||||
|
||||
**Files to Create:**
|
||||
- `src/Scanner/__Tests/StellaOps.Scanner.Sources.Tests/Triggers/SourceTriggerDispatcherTests.cs`
|
||||
- `src/Scanner/__Tests/StellaOps.Scanner.Sources.Tests/Handlers/*Tests.cs`
|
||||
- `src/Scanner/__Tests/StellaOps.Scanner.Sources.Tests/Webhooks/WebhookEndpointsTests.cs`
|
||||
|
||||
---
|
||||
|
||||
## Delivery Tracker
|
||||
|
||||
| Task | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| T1: Trigger Dispatcher | TODO | |
|
||||
| T2: Handler Interface | TODO | |
|
||||
| T3: Zastava Handler | TODO | |
|
||||
| T4: Docker Handler | TODO | |
|
||||
| T5: Git Handler | TODO | |
|
||||
| T6: CLI Handler | TODO | |
|
||||
| T7: Webhook Endpoints | TODO | |
|
||||
| T8: Scheduler Integration | TODO | |
|
||||
| T9: Retry Handler | TODO | |
|
||||
| T10: Tests | TODO | |
|
||||
|
||||
---
|
||||
|
||||
## Next Sprint
|
||||
|
||||
**SPRINT_1229_003_FE_sbom-sources-ui** - Frontend Sources Manager:
|
||||
- Sources list page with status indicators
|
||||
- Add/Edit source wizard per type
|
||||
- Connection test UI
|
||||
- Source detail page with run history
|
||||
Reference in New Issue
Block a user