54 lines
2.4 KiB
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
|
|
}
|