package runner import ( "errors" "fmt" "io" "io/fs" "os" "textmachine/platform/internal/ingest" ) // artifacts.go: the engine's FILE channels, as opposed to its command channels. // // The bank is the one read-out the engine publishes as a file rather than on stdout (D39.122): it // lives beside the project database, is written atomically, and can therefore be read while a run is // going — which is exactly what a signing stop needs, since the stop IS a live run waiting. // ErrNoBank is a book that has no bank read-out yet: it has never been through a run that produced // one. An ordinary state and not a failure — and the caller saves NOTHING on it: an empty bank // written over a real one would erase a read-out the engine has simply not made yet. var ErrNoBank = errors.New("runner: this book has no bank read-out") // maxBank bounds the read-out — three orders of magnitude above any real bank, so it refuses a file // that is not one rather than sizing one that is. const maxBank = 256 << 20 // ReadBank decodes a book's whole-bank read-out at the path the ENGINE published for it — the // `artifacts.bank_export` of its manifest and status documents (row 213). // // The path is taken rather than derived: this function used to parse `book.yaml` and re-derive // `.bank.json` by copying the engine's default from backend/internal/config/book.go, // which is another zone's convention — a changed default there would have silently walked this read // off to a path where no file ever appears. func ReadBank(path string) (ingest.Bank, error) { if path == "" { // An engine build from before the artifact envelope (D39.158). Loud, because the quiet // alternative is a bank that silently never refreshes on such a deployment. return ingest.Bank{}, errors.New("runner: the engine published no bank read-out path: its manifest carries no artifacts envelope") } f, err := os.Open(path) if errors.Is(err, fs.ErrNotExist) { return ingest.Bank{}, ErrNoBank } if err != nil { return ingest.Bank{}, fmt.Errorf("runner: open bank read-out: %w", err) } defer f.Close() raw, err := io.ReadAll(io.LimitReader(f, maxBank+1)) if err != nil { return ingest.Bank{}, fmt.Errorf("runner: read bank read-out: %w", err) } if len(raw) > maxBank { return ingest.Bank{}, errors.New("runner: the bank read-out is larger than this platform will read") } return ingest.DecodeBank(raw) }