103 lines
6.2 KiB
Go
103 lines
6.2 KiB
Go
package ingest
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"textmachine/platform/internal/money"
|
|
)
|
|
|
|
// 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 itself,
|
|
// routing, content labels and the operator's flag taxonomy are engine vocabulary with no consumer
|
|
// on this side. The rule the list enforces is about the WIRE, not the seam: §2.12 forbids these
|
|
// words from reaching a client, and money reaches an INFO log in no form at all (PD-99) — crossing
|
|
// the seam into the credit and consent machinery is legal and precedented
|
|
// (Spend and Reserved below; the re-bill pair, D39.165 §3). An earlier edition of this header read
|
|
// the rule as "must not cross the seam", which its own field comments had already corrected — the
|
|
// refuter of P10 caught the two readings standing side by side. Unknown JSON fields are simply
|
|
// ignored by encoding/json.
|
|
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"`
|
|
// Progress is the PER-WAVE split of the same units, landed in the engine on 09.08 (D39.122;
|
|
// pipeline.PhaseProgress). Taking it is what makes the resync channel as fine-grained as the
|
|
// stream: without it a repair could restore only the aggregate, and folding one aggregate over a
|
|
// projection the stream had already split REPLACED "draft 7/20 ∥ edit 1/20" with one number.
|
|
// Same shape as the stream's own progress event, so both paths materialize through one type.
|
|
Progress Progress `json:"progress"`
|
|
// 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"`
|
|
// The engine's re-bill projection (rebill_units/rebill_usd) is deliberately NOT taken — but the
|
|
// REASON changed under this comment, and both halves of what it used to say are now retired
|
|
// (register row PD-427).
|
|
//
|
|
// ⚠ The old reason was TIMING, and it was true when it was written: status projected the STORED
|
|
// memory, so right after a `bank-apply` — the one moment a consent would want the figure — it
|
|
// honestly read zero (P10 errata 28.08-к). That blind window is CLOSED: the engine's read path
|
|
// now folds the bank first and falls back to the stored glossary only if it cannot
|
|
// (`pipeline.projectStoredMemory`, "IT IS NO LONGER THE READ PATH'S FIRST ANSWER", landing
|
|
// 6ec9f8a / D39.170). `status` will price a re-pass BEFORE it is bought, and still writes nothing.
|
|
//
|
|
// ⚠ The old comment also predicted the SHAPE of the fix — "the pair returns with the engine verb
|
|
// that can fold and price a correction outside a run" — and that is the half worth correcting
|
|
// loudest, because a wrong REASON gets re-checked while a wrong EXPECTATION gets used as a map.
|
|
// No such verb was built and none is planned: the existing `status` was fixed instead.
|
|
//
|
|
// Why the pair is still not taken TODAY: its wiring is gated together with the engine's
|
|
// `translate --max-units`, which waits on a decision of its own (the volume ceiling was broken on
|
|
// a book that mines its bank; the cure landed, the wiring did not). So the figure exists and the
|
|
// seam simply does not carry it yet — which is a different sentence from the one this comment
|
|
// used to make, and the difference is what a later session would otherwise design against.
|
|
// The consents are meanwhile funded from the run's own hold.
|
|
// Spend is the engine's committed spend, converted to integer micro-USD AT THE SEAM. The wire
|
|
// value is a JSON decimal; binding it to a float64 would put drift one step before the integer
|
|
// column that exists to prevent drift (PD-15). It is stored in the credit tables and never
|
|
// projected into an INFO log (PD-99).
|
|
//
|
|
// ⚠ NOR INTO A RESPONSE, and the reason is now the narrow one rather than the blanket. The
|
|
// blanket prohibition was the owner's own and the owner REVOKED it (D39.196 §2): a balance, an
|
|
// order's ceiling and a hold go out in dollars. This figure is none of those — it is what the
|
|
// engine has SPENT, the settled cost of calls, which ПТ-33/ПТ-35 still keep inside (D39.203 draws
|
|
// the line: a pre-call ESTIMATE may go out, a settled cost may not).
|
|
// A POINTER: absent, null and empty must not read as "the attempt cost nothing". A settlement
|
|
// computed from a missing figure would release the whole hold and charge zero.
|
|
Spend *money.MicroUSD `json:"committed_usd"`
|
|
// Reserved is what the engine's OWN ledger has promised and not yet committed. It is NOT part of
|
|
// the ceiling the platform computes — see runs.meter.bookCap: at spawn there is no other writer,
|
|
// so anything reserved is a leftover of a dead process, and the engine's own write-open clears it
|
|
// before judging anything. What this figure does is prove that reasoning holds, which is why the
|
|
// spawn REQUIRES it and refuses without it.
|
|
// A POINTER for the same reason Spend is one: absent, null and zero are three different facts.
|
|
Reserved *money.MicroUSD `json:"reserved_usd"`
|
|
Chapters []ChapterStatus `json:"chapters"`
|
|
}
|
|
|
|
// ChapterStatus is the per-chapter passport, allowlisted the same way (no cost, no verdict ranks).
|
|
//
|
|
// worst_flag_reason is deliberately NOT taken: contract v0 gives a chapter a note_count and nothing
|
|
// about the worst reason, so materializing it would store engine vocabulary no reader asks for
|
|
// (PD-19). It comes back with a column the day the chapter screen needs it.
|
|
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"`
|
|
}
|
|
|
|
// 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
|
|
}
|