Tamp.Ingest.V1
0.1.1
Prefix Reserved
See the version list below for details.
dotnet add package Tamp.Ingest.V1 --version 0.1.1
NuGet\Install-Package Tamp.Ingest.V1 -Version 0.1.1
<PackageReference Include="Tamp.Ingest.V1" Version="0.1.1" />
<PackageVersion Include="Tamp.Ingest.V1" Version="0.1.1" />
<PackageReference Include="Tamp.Ingest.V1" />
paket add Tamp.Ingest.V1 --version 0.1.1
#r "nuget: Tamp.Ingest.V1, 0.1.1"
#:package Tamp.Ingest.V1@0.1.1
#addin nuget:?package=Tamp.Ingest.V1&version=0.1.1
#tool nuget:?package=Tamp.Ingest.V1&version=0.1.1
Tamp.Ingest.V1
Typed C# client + DTOs for the
tamp-ingest-v1egress contract — the canonical wire shape every Tamp-driven build pushes to a compliant sink (tamp.findings, DefectDojo bridge, evidence vault, …).
| Package | Status |
|---|---|
Tamp.Ingest.V1 |
0.1.0 (initial) |
Install
dotnet add package Tamp.Ingest.V1
Multi-targets net8 / net9 / net10. Pulls in Tamp.Http (auth + base HTTP client), Tamp.Sarif (findings type graph), and Tamp.Sbom (CycloneDX type graph) — the latter two so SARIF + SBOM bodies serialize via the canonical writers rather than the base client's generic JSON path.
When to use this
Use Tamp.Ingest.V1 when your build needs to push findings / SBOMs / coverage / test results / scan-run telemetry to any sink that speaks the tamp-ingest-v1 contract. The first server-side consumer is tamp.findings; any compliant sink (your own, a partner's, an enterprise pipeline bridge) plugs in by accepting the same shapes.
The Tamp framework itself does not import this package — the contract is independent of the framework, so adopters who reject Tamp.Build can still ship to a tamp-ingest-v1-speaking sink from any .NET build path.
Quick start
using Tamp;
using Tamp.Ingest.V1;
using Tamp.Sarif;
// One client per build, one bearer token (cli_… or prj_…).
using var ingest = new TampIngestClient(
new Uri("https://tamp-findings.brewingcoder.com"),
new Secret("ingest-token", Environment.GetEnvironmentVariable("TAMP_INGEST_TOKEN")!));
// The hierarchy tuple identifies every payload below.
var ctx = new IngestBuildContext
{
Client = "Tamp",
Project = "tamp",
Component = "tamp",
Version = "1.13.0",
Flavor = "net10",
CommitSha = Environment.GetEnvironmentVariable("GITHUB_SHA"),
Branch = Environment.GetEnvironmentVariable("GITHUB_REF_NAME"),
BuildId = Environment.GetEnvironmentVariable("GITHUB_RUN_ID"),
};
// Push a SARIF file produced by a scanner (e.g. Tamp.OpenGrep / Tamp.Trivy).
var sarif = SarifReader.ReadFromFile("artifacts/security/opengrep.sarif");
await ingest.PostFindingsAsync(ctx, ScannerKind.OpenGrep, sarif);
// Push the aggregate coverage rollup.
await ingest.PostCoverageAsync(ctx, new CoverageIngestRequestDto
{
Format = "cobertura",
LinePercent = 87.3m,
BranchPercent = 71.2m,
});
// Push the per-scanner receipt set so the sink's "last scan" board stays current.
await ingest.PostScanRunsAsync(ctx, new[]
{
new ScanRunReceipt
{
Scanner = ScannerKind.OpenGrep,
StartedUtc = startedUtc,
CompletedUtc = DateTimeOffset.UtcNow,
ExitCode = 0,
FindingsTotal = 0,
},
});
Endpoint surface
Every method takes an IngestBuildContext (encoded as query params; clientName / projectName / componentName / versionString plus optional flavorName / commitSha / branchName / pullRequestRef / buildId) — except the snapshot-scoped provenance endpoint.
| Method | HTTP | Path | Body |
|---|---|---|---|
PostSbomAsync |
POST | /ingest/sbom |
CycloneDX BOM (via SbomWriter) → returns SbomSnapshotResponse |
PostSbomProvenanceAsync |
POST | /ingest/sbom/{snapshotId}/provenance |
raw SLSA / in-toto provenance JSON |
PostFindingsAsync |
POST | /ingest/findings |
SARIF 2.1.0 (via SarifWriter); adds &scannerKind=<kind> |
PostCoverageAsync |
POST | /ingest/coverage |
CoverageIngestRequestDto |
PostTestResultsAsync |
POST | /ingest/test-results |
TestResultsIngestRequestDto |
PostScanRunsAsync |
POST | /ingest/scan-runs |
IReadOnlyList<ScanRunReceipt> (no-op when empty) |
PostSbomVulnerabilitiesAsync |
POST | /ingest/sbom-vulnerabilities |
IReadOnlyList<SbomVulnerability> (no-op when empty) |
POST collection endpoints are intentionally no-op on empty input — caller doesn't need to guard.
The hierarchy tuple
Client → Project → Component → ComponentVersion
└── Flavor (optional, e.g. net10 / web / backend)
Clientis enforced against the bearer token's bound client scope. Acli_token authorizes ingest under any project beneath ONE client; aprj_token is project-scoped.Project/Componentare upserted on first ingest — no out-of-band registration needed.Branch=main/masteris treated as canonical by sinks; others as non-canonical.PullRequestRefset ⇒ the build is preview-scoped and does NOT count as canonical.
Bodies: how serialization works
- SARIF / SBOM bodies are produced by
SarifWriter.Serialize(log)/SbomWriter.Serialize(bom)and shipped as pre-formed JSON. This preserves the case-sensitive wire shape both standards require (e.g. SARIF's$schema, lowercase property names). - All other DTOs (coverage, test-results, scan-runs, sbom-vulnerabilities) serialize via the inherited
TampApiClientJSON path: camelCase property names,nullvalues dropped.Severity/ScannerKindenums serialize as strings.
Auth
var token = new Secret("ingest-token", Environment.GetEnvironmentVariable("TAMP_INGEST_TOKEN")!);
using var ingest = new TampIngestClient(baseUri, token);
Auth is always bearer, wrapped in Tamp.Secret so the token never lands in process listings, log output, or ToString calls (the TAMP004 analyzer flags any Reveal() outside the framework's approved boundary).
Error handling
Failed responses surface as Tamp.Http.ApiException:
- 4xx →
Tamp.Http.ApiClientException(caller fault; don't retry without changing the request) - 5xx →
Tamp.Http.ApiServerException(IsTransient == true; safe to retry with backoff)
The response body is captured and exposed on ResponseBody (truncated above MaxCapturedErrorBodyBytes, default 16 KiB).
Adopter pattern — wiring into a Build.cs
Target Ingest => _ => _
.DependsOn(nameof(Security)) // SARIF + scan-run files already on disk
.Requires(() => IngestToken != null)
.Executes(async () =>
{
using var ingest = new TampIngestClient(IngestBaseUri, IngestToken);
var ctx = new IngestBuildContext
{
Client = "MyClient", Project = "my-project",
Component = ComponentName, Version = GitVersion.NuGetVersionV2,
Flavor = NetFlavor, CommitSha = Git.Commit,
Branch = Git.Branch, BuildId = Environment.GetEnvironmentVariable("GITHUB_RUN_ID"),
};
foreach (var sarif in (Artifacts / "security").GlobFiles("*.sarif"))
{
var scanner = ResolveScannerFromFileName(sarif);
await ingest.PostFindingsAsync(ctx, scanner, SarifReader.ReadFromFile(sarif));
}
await ingest.PostScanRunsAsync(ctx, LoadReceipts());
});
Make the target skippable when IngestToken is null — the contract is opt-in per build, and local dev should not be forced to push.
License
MIT — see LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Tamp.Core (>= 1.13.0)
- Tamp.Http (>= 0.1.1)
- Tamp.Sarif (>= 1.13.0)
- Tamp.Sbom (>= 1.13.0)
-
net8.0
- Tamp.Core (>= 1.13.0)
- Tamp.Http (>= 0.1.1)
- Tamp.Sarif (>= 1.13.0)
- Tamp.Sbom (>= 1.13.0)
-
net9.0
- Tamp.Core (>= 1.13.0)
- Tamp.Http (>= 0.1.1)
- Tamp.Sarif (>= 1.13.0)
- Tamp.Sbom (>= 1.13.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.