Complete compatibility purge audit
This commit is contained in:
@@ -29,7 +29,6 @@ const (
|
||||
ArtifactBoundsSession = "narratio.bounds.session"
|
||||
|
||||
ArtifactProvenancePreviousCacheManifestInput = "manifest.inputs.previous_cache"
|
||||
ArtifactProvenancePreviousCacheFilesystem = "current_session.previous_cache"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -264,54 +263,49 @@ func ResolvePreviousSessionArtifactWithCatalog(
|
||||
return ResolvedSessionArtifact{}, fmt.Errorf("previous-session artifact source %q requires runtime artifact catalog", source)
|
||||
}
|
||||
|
||||
configuredSourceID := ConfiguredArtifactSourceID(artifactName)
|
||||
entry, ok := catalog.Lookup(configuredSourceID)
|
||||
if !ok {
|
||||
if _, ok := catalog.Lookup(ConfiguredArtifactSourceID(artifactName)); !ok {
|
||||
return ResolvedSessionArtifact{}, fmt.Errorf("unsupported previous-session artifact source %q", source)
|
||||
}
|
||||
candidates := previousSessionCacheCandidatePaths(paths, entry.CanonicalRelPath)
|
||||
if len(candidates) == 0 {
|
||||
if m == nil {
|
||||
return ResolvedSessionArtifact{}, &SessionArtifactNotFoundError{ArtifactID: source}
|
||||
}
|
||||
|
||||
manifestInputPaths := manifestInputPathSet(paths, m)
|
||||
fallback := ""
|
||||
for _, candidate := range candidates {
|
||||
exists, isDir, statErr := pathExists(candidate)
|
||||
if statErr != nil {
|
||||
return ResolvedSessionArtifact{}, fmt.Errorf("stat %q: %w", candidate, statErr)
|
||||
}
|
||||
if !exists || isDir {
|
||||
var selected manifest.InputRecord
|
||||
count := 0
|
||||
for _, input := range m.Inputs {
|
||||
if input.Kind != "previous_artifact" || input.Source != source {
|
||||
continue
|
||||
}
|
||||
if err := validateResolvedContent(candidate, contentText); err != nil {
|
||||
return ResolvedSessionArtifact{}, fmt.Errorf("validate %q: %w", source, err)
|
||||
candidate := filepath.Clean(ResolveSessionLocalPathForRead(paths, input.Path))
|
||||
if strings.TrimSpace(candidate) == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := manifestInputPaths[candidate]; ok {
|
||||
return ResolvedSessionArtifact{
|
||||
ID: source,
|
||||
Path: candidate,
|
||||
ProducerStage: "prepare",
|
||||
OutputKind: "previous_session_cache",
|
||||
Provenance: ArtifactProvenancePreviousCacheManifestInput,
|
||||
}, nil
|
||||
}
|
||||
if fallback == "" {
|
||||
fallback = candidate
|
||||
if _, err := pathsafe.SlashRelativeFromRoot(paths.Root, candidate); err != nil {
|
||||
continue
|
||||
}
|
||||
selected, count = input, count+1
|
||||
}
|
||||
|
||||
if fallback != "" {
|
||||
return ResolvedSessionArtifact{
|
||||
ID: source,
|
||||
Path: fallback,
|
||||
ProducerStage: "prepare",
|
||||
OutputKind: "previous_session_cache",
|
||||
Provenance: ArtifactProvenancePreviousCacheFilesystem,
|
||||
}, nil
|
||||
if count != 1 {
|
||||
return ResolvedSessionArtifact{}, &SessionArtifactNotFoundError{ArtifactID: source}
|
||||
}
|
||||
|
||||
return ResolvedSessionArtifact{}, &SessionArtifactNotFoundError{ArtifactID: source}
|
||||
path := filepath.Clean(ResolveSessionLocalPathForRead(paths, selected.Path))
|
||||
if err := requireSafeSessionRegularFile(paths.Root, path); err != nil {
|
||||
return ResolvedSessionArtifact{}, fmt.Errorf("validate %q: unsafe previous-session artifact: %w", source, err)
|
||||
}
|
||||
if err := validateResolvedContent(path, contentText); err != nil {
|
||||
return ResolvedSessionArtifact{}, fmt.Errorf("validate %q: %w", source, err)
|
||||
}
|
||||
checksum := strings.TrimSpace(selected.Checksum)
|
||||
if len(checksum) != 64 || strings.ToLower(checksum) != checksum {
|
||||
return ResolvedSessionArtifact{}, fmt.Errorf("validate %q: previous-session artifact checksum is invalid", source)
|
||||
}
|
||||
actualChecksum, err := SHA256File(path)
|
||||
if err != nil {
|
||||
return ResolvedSessionArtifact{}, fmt.Errorf("checksum %q: %w", source, err)
|
||||
}
|
||||
if actualChecksum != checksum {
|
||||
return ResolvedSessionArtifact{}, fmt.Errorf("validate %q: checksum mismatch", source)
|
||||
}
|
||||
return ResolvedSessionArtifact{ID: source, Path: path, ProducerStage: "prepare", OutputKind: "previous_session_cache", Provenance: ArtifactProvenancePreviousCacheManifestInput, Checksum: checksum}, nil
|
||||
}
|
||||
|
||||
func manifestArtifactCandidate(paths SessionPaths, m *manifest.Manifest, spec artifactSpec) (ResolvedSessionArtifact, error) {
|
||||
@@ -384,65 +378,6 @@ func requireSafeSessionRegularFile(root, path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func previousSessionCacheCandidatePaths(paths SessionPaths, canonicalRelPath string) []string {
|
||||
trimmed := strings.TrimSpace(canonicalRelPath)
|
||||
if trimmed == "" {
|
||||
return nil
|
||||
}
|
||||
normalized := filepath.ToSlash(filepath.Clean(filepath.FromSlash(trimmed)))
|
||||
if normalized == "." || normalized == "" || normalized == ".." || strings.HasPrefix(normalized, "../") || strings.HasPrefix(normalized, "/") {
|
||||
return nil
|
||||
}
|
||||
|
||||
relCandidates := []string{normalized}
|
||||
const artifactsPrefix = "artifacts/"
|
||||
if strings.HasPrefix(normalized, artifactsPrefix) && len(normalized) > len(artifactsPrefix) {
|
||||
relCandidates = append(relCandidates, strings.TrimPrefix(normalized, artifactsPrefix))
|
||||
}
|
||||
|
||||
out := make([]string, 0, len(relCandidates))
|
||||
seen := map[string]struct{}{}
|
||||
for _, rel := range relCandidates {
|
||||
abs, err := SessionPreviousArtifactPath(paths, rel)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
abs = filepath.Clean(abs)
|
||||
if _, ok := seen[abs]; ok {
|
||||
continue
|
||||
}
|
||||
seen[abs] = struct{}{}
|
||||
out = append(out, abs)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func manifestInputPathSet(paths SessionPaths, m *manifest.Manifest) map[string]struct{} {
|
||||
if m == nil || len(m.Inputs) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make(map[string]struct{}, len(m.Inputs))
|
||||
for _, in := range m.Inputs {
|
||||
resolved := filepath.Clean(ResolveSessionLocalPathForRead(paths, in.Path))
|
||||
if strings.TrimSpace(resolved) == "" {
|
||||
continue
|
||||
}
|
||||
out[resolved] = struct{}{}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func pathExists(path string) (exists bool, isDir bool, err error) {
|
||||
info, err := os.Stat(path)
|
||||
if err == nil {
|
||||
return true, info.IsDir(), nil
|
||||
}
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return false, false, nil
|
||||
}
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
func validateResolvedContent(path string, kind artifactContentKind) error {
|
||||
switch kind {
|
||||
case contentTranscriptJSON:
|
||||
|
||||
Reference in New Issue
Block a user