Files

130 lines
3.7 KiB
Go
Raw Permalink Normal View History

package artifacts
import (
"context"
"errors"
"fmt"
"strings"
"gitea.maximumdirect.net/eric/narratio/internal/adapters/storage"
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
)
var (
2026-09-02 13:47:20 +00:00
ErrCurrentCommitPointerMissing = errors.New("current commit pointer missing")
)
2026-09-02 13:47:20 +00:00
type CurrentCommitPointerMissingError struct {
Key string
}
2026-09-02 13:47:20 +00:00
func (e *CurrentCommitPointerMissingError) Error() string {
return fmt.Sprintf("%s: %q; republish this session to establish committed remote state", ErrCurrentCommitPointerMissing, e.Key)
}
2026-09-02 13:47:20 +00:00
func (e *CurrentCommitPointerMissingError) Unwrap() error {
return ErrCurrentCommitPointerMissing
}
type CurrentState struct {
SessionPrefix string
CurrentManifestKey string
2026-08-10 19:47:12 +00:00
CurrentPointerKey string
RunID string
Manifest *manifest.Manifest
2026-08-10 21:20:59 +00:00
ManifestData []byte
2026-08-10 19:47:12 +00:00
Commit *RemoteCommitManifest
Pointer *CurrentCommitPointer
}
type CurrentStateValidation struct {
ExpectedCampaign string
ExpectedSessionID string
ExpectedRunID string
ValidateRunID bool
}
func LoadCurrentState(
ctx context.Context,
store storage.ObjectStore,
sessionPrefix string,
validation CurrentStateValidation,
) (*CurrentState, error) {
prefix := strings.TrimSpace(sessionPrefix)
if prefix == "" {
return nil, fmt.Errorf("session prefix is required")
}
2026-08-10 19:47:12 +00:00
if store == nil {
return nil, fmt.Errorf("object store is required")
}
2026-08-10 19:47:12 +00:00
pointerKey := S3CurrentCommitPointerKey(prefix)
pointerExists, err := store.Exists(ctx, pointerKey)
if err != nil {
return nil, fmt.Errorf("check current commit pointer %q: %w", pointerKey, err)
}
2026-09-02 13:47:20 +00:00
if !pointerExists {
return nil, &CurrentCommitPointerMissingError{Key: pointerKey}
}
2026-09-02 13:47:20 +00:00
return loadCommittedCurrentState(ctx, store, prefix, pointerKey, validation)
}
func ValidateCurrentStateIdentity(state *CurrentState, validation CurrentStateValidation) error {
if state == nil || state.Manifest == nil {
return fmt.Errorf("current state with manifest is required")
}
expectedSessionID := strings.TrimSpace(validation.ExpectedSessionID)
expectedCampaign := strings.TrimSpace(validation.ExpectedCampaign)
expectedRunID := strings.TrimSpace(validation.ExpectedRunID)
manifestSessionID := strings.TrimSpace(state.Manifest.SessionID)
manifestCampaign := strings.TrimSpace(state.Manifest.Campaign)
manifestRunID := strings.TrimSpace(state.Manifest.RunID)
if manifestCampaign != "" {
if err := ValidateSessionIdentity(manifestCampaign, manifestSessionID); err != nil {
2026-09-02 13:47:20 +00:00
return fmt.Errorf("current manifest contains unsafe identities: %w", err)
}
}
if manifestRunID != "" {
if err := ValidateRunIdentity(manifestRunID); err != nil {
2026-09-02 13:47:20 +00:00
return fmt.Errorf("current manifest contains an unsafe run id: %w", err)
}
}
if expectedSessionID != "" && manifestSessionID != expectedSessionID {
return fmt.Errorf(
"current manifest session_id %q does not match expected session_id %q",
manifestSessionID,
expectedSessionID,
)
}
if expectedCampaign != "" {
if manifestCampaign == "" {
return fmt.Errorf("current manifest campaign is required")
}
if manifestCampaign != expectedCampaign {
return fmt.Errorf(
"current manifest campaign %q does not match expected campaign %q",
manifestCampaign,
expectedCampaign,
)
}
}
if expectedRunID == "" && validation.ValidateRunID {
expectedRunID = strings.TrimSpace(state.RunID)
}
if expectedRunID != "" {
if manifestRunID == "" {
return fmt.Errorf("current manifest run_id is required")
}
if manifestRunID != expectedRunID {
return fmt.Errorf(
"current run pointer %q references run %q but current manifest run_id is %q",
2026-09-02 13:47:20 +00:00
state.CurrentPointerKey,
expectedRunID,
manifestRunID,
)
}
}
return nil
}