100 lines
3.5 KiB
Go
100 lines
3.5 KiB
Go
package artifacts
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"gitea.maximumdirect.net/eric/narratio/internal/artifactmodel"
|
|
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
|
|
)
|
|
|
|
// MaxExtractionPayloadBytes bounds one validated Notarius lane or index payload.
|
|
const MaxExtractionPayloadBytes int64 = 64 * 1024 * 1024
|
|
|
|
const (
|
|
extractStageName = "extract"
|
|
extractionLaneKind = "notarius_lane"
|
|
extractionIndexKind = "notarius_index"
|
|
extractionMetadataRun = "narratio_run_id"
|
|
extractionMetadataRoot = "bundle_root"
|
|
)
|
|
|
|
// HydrateExtractionArtifacts marks extraction sources available only when the current
|
|
// manifest contains one complete, internally consistent, succeeded extraction bundle.
|
|
// Invalid, stale, incomplete, or unsafe records leave every extraction source unavailable.
|
|
func (c *ArtifactCatalog) HydrateExtractionArtifacts(
|
|
paths SessionPaths,
|
|
m *manifest.Manifest,
|
|
configured map[string]ExtractionArtifactDefinition,
|
|
) {
|
|
if c == nil || m == nil || len(configured) == 0 {
|
|
return
|
|
}
|
|
proof := InspectExtractionEvidence(paths, m, configured)
|
|
if proof.State != ExtractionEvidenceValid {
|
|
return
|
|
}
|
|
for sourceID, path := range proof.Outputs {
|
|
_ = c.markAvailableFromExtractManifest(sourceID, path, proof.ProducerRunID)
|
|
}
|
|
}
|
|
|
|
func compatibleCatalogExtractionContract(got *artifactmodel.ContractMetadata, want ExtractionArtifactDefinition) bool {
|
|
return got != nil && got.MediaType == want.MediaType && got.SchemaID == want.SchemaID &&
|
|
got.SchemaVersion == want.SchemaVersion && (want.ModuleKey == "" || got.ModuleKey == want.ModuleKey)
|
|
}
|
|
|
|
func compatibleCatalogExtractionProvenance(
|
|
got *artifactmodel.ExternalProvenance,
|
|
runID, pipelineID string,
|
|
want ExtractionArtifactDefinition,
|
|
) bool {
|
|
return got != nil && got.System == "notarius" && got.RunID == runID && got.PipelineID == pipelineID &&
|
|
pipelineID == strings.TrimSpace(want.PipelineID) && got.ArtifactID == want.LaneID
|
|
}
|
|
|
|
func safeExistingExtractionDirectory(sessionRoot, bundleRoot string) bool {
|
|
if !pathWithinExtractionRoot(sessionRoot, bundleRoot) || !safeExtractionComponents(sessionRoot, bundleRoot) {
|
|
return false
|
|
}
|
|
info, err := os.Lstat(bundleRoot)
|
|
return err == nil && info.IsDir() && info.Mode()&os.ModeSymlink == 0
|
|
}
|
|
|
|
func safeExtractionComponents(root, target string) bool {
|
|
relative, err := filepath.Rel(filepath.Clean(root), filepath.Clean(target))
|
|
if err != nil || relative == "." || relative == ".." || strings.HasPrefix(relative, ".."+string(filepath.Separator)) {
|
|
return false
|
|
}
|
|
current := filepath.Clean(root)
|
|
for _, part := range strings.Split(relative, string(filepath.Separator)) {
|
|
current = filepath.Join(current, part)
|
|
info, err := os.Lstat(current)
|
|
if err != nil || info.Mode()&os.ModeSymlink != 0 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func pathWithinExtractionRoot(root, target string) bool {
|
|
relative, err := filepath.Rel(filepath.Clean(root), filepath.Clean(target))
|
|
return err == nil && relative != "." && relative != ".." && !strings.HasPrefix(relative, ".."+string(filepath.Separator))
|
|
}
|
|
|
|
func safeExtractionPathSegment(value string) bool {
|
|
return value != "" && value != "." && value != ".." && filepath.Base(value) == value &&
|
|
!strings.ContainsAny(value, `/\\`)
|
|
}
|
|
|
|
func extractionMetadataString(metadata map[string]any, key string) string {
|
|
value, _ := metadata[key].(string)
|
|
return strings.TrimSpace(value)
|
|
}
|
|
|
|
func extractionReceiptIdentity(metadata map[string]any) (string, string) {
|
|
receipt, _ := metadata["receipt"].(map[string]any)
|
|
return extractionMetadataString(receipt, "run_id"), extractionMetadataString(receipt, "pipeline_id")
|
|
}
|