package ingest import ( "encoding/json" "fmt" ) // StatusReport is the ALLOWLISTED subset of `tmctl status --json` (pipeline.StatusReport) that the // platform materializes. Everything absent here is absent on purpose: snapshot ids, drift, rebill, // routing, content labels and the operator's flag taxonomy are engine vocabulary that must not // cross the seam (contract §2.12), and unknown JSON fields are simply ignored by encoding/json. // // ⚠ One field is money, and it is here for metering only — see SpendUSD. // // ⚠ Limit worth knowing: status has no PHASE split (engine backlog row 99). A resync can therefore // restore the aggregate counter but not "draft N/M ∥ edit N/M"; the phase split lives only in the // stream until row 99 lands. A reconciled run shows the aggregate until its next progress event. type StatusReport struct { BookID string `json:"book_id"` TotalUnits int `json:"total_units"` Done int `json:"done"` InProgress int `json:"in_progress"` Flagged int `json:"flagged"` Pending int `json:"pending"` // ETASeconds is what the owner asked to show (K-5); the engine already computes it. ETASeconds float64 `json:"eta_seconds"` // UnsignedBankTerms backs the signing screen's "N of M decided" while a stop is standing. UnsignedBankTerms int `json:"unsigned_bank_terms"` // SpendUSD is the engine's committed spend. The platform meters usage from its DELTA between // attempts, because the event stream deliberately carries no figures. It is stored in the // usage tables and NEVER projected into an API response or an INFO log (D39.84). SpendUSD float64 `json:"committed_usd"` Chapters []ChapterStatus `json:"chapters"` } // ChapterStatus is the per-chapter passport, allowlisted the same way (no cost, no verdict ranks). type ChapterStatus struct { Chapter int `json:"chapter"` UnitsTotal int `json:"units_total"` UnitsDone int `json:"units_done"` UnitsFlagged int `json:"units_flagged"` UnitsInProgress int `json:"units_in_progress"` UnitsPending int `json:"units_pending"` // WorstFlagReason is engine vocabulary: stored, mapped to a product phrase at read time, never // projected raw. WorstFlagReason string `json:"worst_flag_reason"` } // DecodeStatus parses a status report. func DecodeStatus(b []byte) (StatusReport, error) { var r StatusReport if err := json.Unmarshal(b, &r); err != nil { return StatusReport{}, fmt.Errorf("ingest: decode status: %w", err) } return r, nil }