Goose 00036 is a renumbered copy of the vault tables. Degraded read stays off by default and only runs after authorization; tombstones and unpublished artifacts fail closed. Co-authored-by: Cursor <cursoragent@cursor.com>
218 lines
6.6 KiB
Go
218 lines
6.6 KiB
Go
package artifactvault
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"chat-agent-system/internal/fsutil"
|
|
)
|
|
|
|
type FilesystemStore struct {
|
|
Root string
|
|
StorageIdentity string
|
|
MasterKey []byte
|
|
}
|
|
|
|
func NewFilesystemStore(root, identity string, master []byte) (*FilesystemStore, error) {
|
|
if strings.TrimSpace(root) == "" || strings.Contains(root, "data/dev") && !strings.Contains(root, "vault") {
|
|
if strings.HasSuffix(filepath.Clean(root), filepath.Join("data", "dev")) {
|
|
return nil, fmt.Errorf("artifactvault: refuse vault root %s (must not be runtime data/dev)", root)
|
|
}
|
|
}
|
|
if identity == "" {
|
|
return nil, fmt.Errorf("artifactvault: storage identity required")
|
|
}
|
|
if err := os.MkdirAll(root, 0o700); err != nil {
|
|
return nil, err
|
|
}
|
|
sent := filepath.Join(root, ".vault-storage-identity.json")
|
|
if b, err := os.ReadFile(sent); err == nil {
|
|
var got map[string]string
|
|
_ = json.Unmarshal(b, &got)
|
|
if got["uuid"] != "" && got["uuid"] != identity {
|
|
return nil, fmt.Errorf("artifactvault: storage identity mismatch")
|
|
}
|
|
} else {
|
|
_ = fsutil.WriteFileAtomic(sent, []byte(`{"uuid":"`+identity+`"}`), 0o444)
|
|
}
|
|
return &FilesystemStore{Root: root, StorageIdentity: identity, MasterKey: master}, nil
|
|
}
|
|
|
|
func (s *FilesystemStore) Identity() string { return s.StorageIdentity }
|
|
|
|
func (s *FilesystemStore) blobPath(env, projectKey, sha string) string {
|
|
return filepath.Join(s.Root, "scopes", env, projectKey, "blobs", "sha256", sha[:2], sha[2:4], sha)
|
|
}
|
|
|
|
func (s *FilesystemStore) PutBlob(env, projectKey, sha string, plain []byte) (keyRef string, err error) {
|
|
if SHA256Hex(plain) != sha {
|
|
return "", fmt.Errorf("artifactvault: blob sha mismatch")
|
|
}
|
|
key, ref, err := ProjectKey(s.MasterKey, env, projectKey)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
envBytes, err := Encrypt(key, plain)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
path := s.blobPath(env, projectKey, sha)
|
|
if _, err := os.Stat(path); err == nil {
|
|
return ref, nil
|
|
}
|
|
if err := writeExclusiveOK(path, envBytes, 0o600); err != nil {
|
|
return "", err
|
|
}
|
|
return ref, nil
|
|
}
|
|
|
|
func (s *FilesystemStore) GetBlob(env, projectKey, sha string) ([]byte, error) {
|
|
key, _, err := ProjectKey(s.MasterKey, env, projectKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
raw, err := os.ReadFile(s.blobPath(env, projectKey, sha))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
plain, err := Decrypt(key, raw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if SHA256Hex(plain) != sha {
|
|
return nil, fmt.Errorf("artifactvault: decrypted sha mismatch")
|
|
}
|
|
return plain, nil
|
|
}
|
|
|
|
func writeExclusiveOK(path string, raw []byte, perm os.FileMode) error {
|
|
if err := fsutil.WriteFileAtomicExclusive(path, raw, perm); err != nil {
|
|
if strings.Contains(err.Error(), "refuse overwrite") {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *FilesystemStore) PutManifest(artifactUUID, manifestSHA string, raw []byte) error {
|
|
path := filepath.Join(s.Root, "manifests", artifactUUID, manifestSHA+".json")
|
|
return writeExclusiveOK(path, raw, 0o644)
|
|
}
|
|
|
|
func (s *FilesystemStore) PutCommit(artifactUUID, manifestSHA string, raw []byte) error {
|
|
path := filepath.Join(s.Root, "commits", artifactUUID, manifestSHA+".json")
|
|
return writeExclusiveOK(path, raw, 0o644)
|
|
}
|
|
|
|
func (s *FilesystemStore) ListCommitted() ([]string, error) {
|
|
dir := filepath.Join(s.Root, "commits")
|
|
ents, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
out := make([]string, 0, len(ents))
|
|
for _, e := range ents {
|
|
if e.IsDir() {
|
|
out = append(out, e.Name())
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (s *FilesystemStore) LoadCommittedArtifacts() ([]Artifact, map[string][]byte, error) {
|
|
uuids, err := s.ListCommitted()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
payloads := make(map[string][]byte)
|
|
arts := make([]Artifact, 0, len(uuids))
|
|
for _, uuid := range uuids {
|
|
if s.HasTombstone(uuid) {
|
|
arts = append(arts, Artifact{UUID: uuid, PublicationStatus: StatusTombstoned})
|
|
continue
|
|
}
|
|
art, body, err := s.loadCommitted(uuid)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
arts = append(arts, art)
|
|
payloads[uuid] = body
|
|
}
|
|
return arts, payloads, nil
|
|
}
|
|
|
|
func (s *FilesystemStore) loadCommitted(uuid string) (Artifact, []byte, error) {
|
|
dir := filepath.Join(s.Root, "commits", uuid)
|
|
ents, err := os.ReadDir(dir)
|
|
if err != nil || len(ents) == 0 {
|
|
return Artifact{}, nil, fmt.Errorf("artifactvault: unpublished")
|
|
}
|
|
raw, err := os.ReadFile(filepath.Join(dir, ents[0].Name()))
|
|
if err != nil {
|
|
return Artifact{}, nil, err
|
|
}
|
|
var marker CommitMarker
|
|
if err := json.Unmarshal(raw, &marker); err != nil {
|
|
return Artifact{}, nil, err
|
|
}
|
|
manRaw, err := os.ReadFile(filepath.Join(s.Root, "manifests", uuid, marker.ManifestSHA256+".json"))
|
|
if err != nil {
|
|
return Artifact{}, nil, err
|
|
}
|
|
var man Manifest
|
|
if err := json.Unmarshal(manRaw, &man); err != nil {
|
|
return Artifact{}, nil, err
|
|
}
|
|
if len(man.PayloadMembers) == 0 {
|
|
return Artifact{}, nil, fmt.Errorf("artifactvault: manifest has no payload members")
|
|
}
|
|
body, err := s.GetBlob(man.Environment, man.ProjectExternalKey, man.PayloadMembers[0].SHA256)
|
|
if err != nil {
|
|
return Artifact{}, nil, err
|
|
}
|
|
var snap, msg int64
|
|
if man.SourceSnapshotID != nil {
|
|
snap = *man.SourceSnapshotID
|
|
}
|
|
if man.SourceMessageID != nil {
|
|
msg = *man.SourceMessageID
|
|
}
|
|
return Artifact{
|
|
UUID: uuid, ArtifactType: man.ArtifactType, LogicalResourceID: man.LogicalResourceID,
|
|
Environment: man.Environment, ProjectID: man.ProjectID, ProjectExternalKey: man.ProjectExternalKey,
|
|
SourceEventID: man.SourceEventID, SourceEventKind: man.SourceEventKind,
|
|
SourceSnapshotID: snap, SourceMessageID: msg, SourceRunID: man.SourceRunID,
|
|
PayloadSHA256: man.PayloadMembers[0].SHA256, ManifestSHA256: man.ManifestSHA256,
|
|
VaultStorageIdentity: man.VaultStorageIdentity, TerminalState: man.TerminalEvidence.State,
|
|
PublicationStatus: StatusVerified, RetentionClass: man.RetentionClass,
|
|
}, body, nil
|
|
}
|
|
|
|
func (s *FilesystemStore) HasCommit(artifactUUID, manifestSHA string) bool {
|
|
_, err := os.Stat(filepath.Join(s.Root, "commits", artifactUUID, manifestSHA+".json"))
|
|
return err == nil
|
|
}
|
|
|
|
func (s *FilesystemStore) PutTombstone(artifactUUID, eventUUID string, raw []byte) error {
|
|
path := filepath.Join(s.Root, "tombstones", artifactUUID, eventUUID+".json")
|
|
return fsutil.WriteFileAtomicExclusive(path, raw, 0o644)
|
|
}
|
|
|
|
func (s *FilesystemStore) HasTombstone(artifactUUID string) bool {
|
|
dir := filepath.Join(s.Root, "tombstones", artifactUUID)
|
|
ents, err := os.ReadDir(dir)
|
|
return err == nil && len(ents) > 0
|
|
}
|
|
|
|
func (s *FilesystemStore) Ready() (identityOK, connected, lagOK bool) {
|
|
_, err := os.Stat(s.Root)
|
|
return s.StorageIdentity != "", err == nil, err == nil
|
|
}
|