textmachine/platform/internal/ingest/manifest.go

54 lines
2.4 KiB
Go

package ingest
import (
"encoding/hex"
"encoding/json"
"fmt"
)
// Manifest is the ALLOWLISTED summary of the engine's chapter manifest (`tmctl manifest --json`,
// unified backlog row 100, form ratified with D39.122 as `tm-manifest-v2`).
//
// What is deliberately absent is most of the document: the per-chapter and per-unit arrays carry the
// engine's stable ids, its dense ordinals and its rendered headings, and none of them has a reader
// on this side yet — the platform's chapter tree is written by the materializer from the stream, not
// from here. Taking them now would store engine vocabulary nobody asks for and would have to be
// maintained against a document that re-cuts itself.
//
// What IS taken is what intake decides with: how many chapters the book has (the ceiling scale is
// built on it), and the two identifiers that say WHICH cut of WHICH bytes produced that number.
type Manifest struct {
// Version is `manifest_version`. Recorded and logged rather than gated: the fields below are
// counts and identities whose meaning is stable, and the engine already refuses the version it
// cannot produce. A shape this platform truly could not read arrives as a zero chapter count,
// which is a loud failure and not a silent one.
Version string `json:"manifest_version"`
ChaptersTotal int `json:"chapters_total"`
UnitsTotal int `json:"units_total"`
// SourceSHA256 is the digest of the ingested source, hex. It answers "is the file on disk still
// the one that was cut" without the platform reading the file again.
SourceSHA256 string `json:"source_sha256"`
SourceBytes int64 `json:"source_bytes"`
// ChunkerVersion is one of the inputs of the manifest's validity key: a change re-numbers
// chapters, so a stored tree is only comparable within one of these (register row PD-166).
ChunkerVersion string `json:"chunker_version"`
}
// SourceSHA256Bytes is the digest as the column stores it. An unparsable value is stored as nothing
// rather than as garbage: the field is evidence, and evidence that cannot be decoded is absence.
func (m Manifest) SourceSHA256Bytes() []byte {
b, err := hex.DecodeString(m.SourceSHA256)
if err != nil {
return nil
}
return b
}
// DecodeManifest parses a manifest document.
func DecodeManifest(b []byte) (Manifest, error) {
var m Manifest
if err := json.Unmarshal(b, &m); err != nil {
return Manifest{}, fmt.Errorf("ingest: decode manifest: %w", err)
}
return m, nil
}