Skip to content
quarry Artifact [6da3069917]
Login

Artifact [6da3069917]

Artifact 6da30699179fb53d410393cfe2099a472c7637998fe29ad5a8c8e82b42ade219:


// Package ingest is the watcher (0003 phase 1): poll zit for artifacts
// since the persisted cursor, parse them against the artifact-format
// RFC, fold them into the index. Cursor loss (store rewritten by pack or
// shun) is detected, never silent: the index resets and re-ingests from
// zero — the same path `quarry reindex` takes on purpose, and the one
// the disposability harness exercises.
package ingest

import (
	"errors"
	"log"

	"craftdesign.group/quarry/internal/artifact"
	"craftdesign.group/quarry/internal/index"
	"craftdesign.group/quarry/internal/zitcli"
)

// Sync runs one poll-parse-fold round. It returns the number of
// artifacts folded (0 when the cursor was current).
func Sync(cli *zitcli.Client, ix *index.Index) (int, error) {
	since, verify, err := ix.Cursor()
	if err != nil {
		return 0, err
	}
	batch, err := cli.Frames(zitcli.Cursor{Since: since, Verify: verify})
	if errors.Is(err, zitcli.ErrCursorInvalid) {
		// Store rewritten under us: drop derived state, bootstrap.
		if err := ix.Reset(); err != nil {
			return 0, err
		}
		batch, err = cli.Frames(zitcli.Cursor{})
	}
	if err != nil {
		return 0, err
	}
	parsed := make([]index.Parsed, 0, len(batch.Artifacts))
	for _, a := range batch.Artifacts {
		art, perr := artifact.Parse(a.Bytes)
		if perr != nil {
			// Passed framing but violates the card grammar: log-worthy,
			// but a store can legitimately hold such bytes as content.
			log.Printf("ingest: %s: treating as content: %v", a.Hash, perr)
		}
		parsed = append(parsed, index.Parsed{Hash: a.Hash, Art: art})
	}
	if err := ix.Fold(parsed, batch.Cursor.Since, batch.Cursor.Verify); err != nil {
		return 0, err
	}
	return len(batch.Artifacts), nil
}