Publish evidence context in JSON output bundles

This commit is contained in:
2026-07-27 18:23:48 +00:00
parent e61e522662
commit 256cc98ddb
6 changed files with 276 additions and 31 deletions

View File

@@ -13,6 +13,7 @@ import (
"gitea.maximumdirect.net/eric/notarius/internal/framework/chunkmap"
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
"gitea.maximumdirect.net/eric/notarius/internal/framework/evidencecontext"
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
)
@@ -22,6 +23,8 @@ const contentTypeJSON = "application/json"
const chunkMapFileName = "chunk-map.json"
const evidenceContextFileName = "evidence-context.json"
var safeOutputFileChar = regexp.MustCompile(`[^A-Za-z0-9._-]`)
var _ contracts.OutputEncoder = (*Encoder)(nil)
@@ -223,14 +226,15 @@ func decodeEvidenceLaneIDs(value any) ([]string, error) {
}
type indexFile struct {
ManifestFile string `json:"manifest_file"`
OutputFiles []outputFileIndex `json:"output_files"`
RejectedFile string `json:"rejected_file"`
WarningsFile string `json:"warnings_file"`
ChunkMap *chunkMapIndex `json:"chunk_map,omitempty"`
ManifestFile string `json:"manifest_file"`
OutputFiles []outputFileIndex `json:"output_files"`
RejectedFile string `json:"rejected_file"`
WarningsFile string `json:"warnings_file"`
ChunkMap *artifactIndex `json:"chunk_map,omitempty"`
EvidenceContext *artifactIndex `json:"evidence_context,omitempty"`
}
type chunkMapIndex struct {
type artifactIndex struct {
ArtifactKind contracts.ArtifactKind `json:"artifact_kind"`
File string `json:"file"`
MediaType string `json:"media_type"`
@@ -264,7 +268,7 @@ func logicalFiles(req contracts.OutputRequest, options Options) ([]contracts.Out
})
outputIndexes := make([]outputFileIndex, 0, len(outputs))
files := make([]contracts.OutputFile, 0, len(outputs)+4)
files := make([]contracts.OutputFile, 0, len(outputs)+5)
manifestFile, err := jsonFile("manifest.json", req.Manifest)
if err != nil {
return nil, err
@@ -311,6 +315,14 @@ func logicalFiles(req contracts.OutputRequest, options Options) ([]contracts.Out
files = append(files, chunkMapOutput)
index.ChunkMap = &chunkMapDescriptor
}
if req.EvidenceContext != nil {
evidenceOutput, evidenceDescriptor, err := serializedEvidenceContextFile(*req.EvidenceContext)
if err != nil {
return nil, err
}
files = append(files, evidenceOutput)
index.EvidenceContext = &evidenceDescriptor
}
indexOutput, err := jsonFile("index.json", index)
if err != nil {
return nil, err
@@ -330,24 +342,24 @@ func logicalFiles(req contracts.OutputRequest, options Options) ([]contracts.Out
return files, nil
}
func serializedChunkMapFile(artifact contracts.SerializedArtifact) (contracts.OutputFile, chunkMapIndex, error) {
func serializedChunkMapFile(artifact contracts.SerializedArtifact) (contracts.OutputFile, artifactIndex, error) {
if artifact.Kind != chunkmap.ArtifactKind {
return contracts.OutputFile{}, chunkMapIndex{}, encoderErrorf("chunk map has unexpected artifact kind %q", artifact.Kind)
return contracts.OutputFile{}, artifactIndex{}, encoderErrorf("chunk map has unexpected artifact kind %q", artifact.Kind)
}
if artifact.Schema.ID != chunkmap.SchemaID || artifact.Schema.Name != chunkmap.SchemaName || artifact.Schema.Version != chunkmap.SchemaVersion {
return contracts.OutputFile{}, chunkMapIndex{}, encoderErrorf("chunk map has unexpected schema identity")
return contracts.OutputFile{}, artifactIndex{}, encoderErrorf("chunk map has unexpected schema identity")
}
if strings.TrimSpace(artifact.MediaType) != chunkmap.MediaType {
return contracts.OutputFile{}, chunkMapIndex{}, encoderErrorf("chunk map has unsupported media type %q", artifact.MediaType)
return contracts.OutputFile{}, artifactIndex{}, encoderErrorf("chunk map has unsupported media type %q", artifact.MediaType)
}
if _, err := chunkmap.New().Decode(artifact.Content); err != nil {
return contracts.OutputFile{}, chunkMapIndex{}, encoderErrorf("decode chunk map: %w", err)
return contracts.OutputFile{}, artifactIndex{}, encoderErrorf("decode chunk map: %w", err)
}
file, err := serializedOutputFile(chunkMapFileName, artifact)
if err != nil {
return contracts.OutputFile{}, chunkMapIndex{}, err
return contracts.OutputFile{}, artifactIndex{}, err
}
return file, chunkMapIndex{
return file, artifactIndex{
ArtifactKind: artifact.Kind,
File: chunkMapFileName,
MediaType: chunkmap.MediaType,
@@ -357,6 +369,32 @@ func serializedChunkMapFile(artifact contracts.SerializedArtifact) (contracts.Ou
}, nil
}
func serializedEvidenceContextFile(artifact contracts.SerializedArtifact) (contracts.OutputFile, artifactIndex, error) {
codec := evidencecontext.New()
expected := codec.Schema()
if artifact.Kind != evidencecontext.ArtifactKind ||
artifact.Schema.ID != expected.ID || artifact.Schema.Name != expected.Name || artifact.Schema.Version != expected.Version ||
contracts.DigestArtifactSchema(artifact.Schema) != contracts.DigestArtifactSchema(expected) ||
strings.TrimSpace(artifact.MediaType) != evidencecontext.MediaType {
return contracts.OutputFile{}, artifactIndex{}, encoderErrorf("evidence context artifact is invalid")
}
if _, err := codec.Decode(artifact.Content); err != nil {
return contracts.OutputFile{}, artifactIndex{}, encoderErrorf("evidence context artifact is invalid")
}
file, err := serializedOutputFile(evidenceContextFileName, artifact)
if err != nil {
return contracts.OutputFile{}, artifactIndex{}, encoderErrorf("evidence context artifact is invalid")
}
return file, artifactIndex{
ArtifactKind: evidencecontext.ArtifactKind,
File: evidenceContextFileName,
MediaType: evidencecontext.MediaType,
SchemaID: expected.ID,
SchemaName: expected.Name,
SchemaVersion: expected.Version,
}, nil
}
func serializedOutputFile(name string, artifact contracts.SerializedArtifact) (contracts.OutputFile, error) {
content := append([]byte(nil), artifact.Content...)
if len(content) == 0 {