textmachine/platform/internal/ingest/resync.go

56 lines
2.5 KiB
Go

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
}