textmachine/platform/internal/ingest/notes.go

83 lines
4.4 KiB
Go

package ingest
// notes.go: the map "engine flag reason → contract note code → step".
//
// It lives here, with the other translations of the seam's vocabulary, because it has two consumers:
// the read path that answers `GET /notes`, and the materializer that puts a note into a stream
// frame. One copy in the HTTP layer left the frame carrying the engine's own word.
//
// ⚠ The codes are the companion's ◆ PROPOSAL (appendix A) and are not ratified — which is why
// `Note.code` is not an enum in the schema. The one code outside it is the placeholder below: the
// appendix leaves that cell empty while the field is required, so the server owes a value (PD-246).
// NoteStep is the contract's NoteSeverity. Two values today; how many there ought to be is an open
// product question (K-6).
const (
StepAttention = "attention"
StepGlance = "glance"
)
// NoteCodeUnspecified is what a reason outside the map projects as.
//
// Not a hole and not an invention: the engine and the platform ship independently, so between two
// releases the engine emits reasons this build has never heard of, and the wire needs a defined
// answer for that window. The contract already says what a client does with a code it does not know.
// Forwarding the engine's word is the leak this map prevents; dropping the note loses a remark.
//
// ⚠ Closing the window needs the ENGINE to publish its flag reasons as data — this module may not
// import it (D39.85) — which is register row PD-246, not a change here.
const NoteCodeUnspecified = "unspecified"
// note is one row of appendix A.
type note struct {
code string
step string
}
// notes mirrors appendix A of the companion.
//
// ⚠ The STEP is decided per row and NOT derived from the engine's severity rank. Deriving it tied
// the wire to another zone's internal triage order — a rank the engine may re-order for its own
// reasons — and put `empty_answer` (nothing usable was delivered) in the same quiet step as
// `markup_cleaned` (the text shipped, tidied). The line drawn here is: did the reader lose text.
// It is the platform's reading and awaits the owner's word with the phrases (column «Ступень» ⬜).
var notes = map[string]note{
"hard_refusal": {"content_withheld", StepAttention},
"soft_refusal": {"content_withheld", StepAttention},
"content_filter": {"content_withheld", StepAttention},
"hard_block": {"content_withheld", StepAttention},
"cjk_artifact": {"source_residue", StepAttention},
"excision_suspect": {"text_possibly_dropped", StepAttention},
"coverage_fail": {"incomplete_coverage", StepAttention},
"sanitizer_defect": {"markup_defect", StepAttention},
"loop_degenerate": {"repetition", StepAttention},
"decode_error": {"unreadable_answer", StepAttention},
"empty": {"empty_answer", StepAttention},
"upstream_not_ok": {"unavailable", StepAttention},
"glossary_miss": {"term_not_applied", StepGlance},
"length": {"length_mismatch", StepGlance},
"sanitizer_stripped": {"markup_cleaned", StepGlance},
// The sixteenth, ratified with contract minor 0.10.0 (D39.194). It is the row PD-246 predicted:
// the engine owns the vocabulary, this map is a hand copy, and the two ship independently — so
// the reason arrived here as `unspecified` until this line existed.
//
// ⚠ The STEP is this side's reading and not the engine's rank, by the rule above: an answer that
// came back in the wrong language is text the reader LOST — nothing of the chapter is usable — so
// it is Attention, alongside `empty_answer`. The canon leaves the column ⬜ awaiting the owner's
// phrases; the reading is recorded here so it can be argued with.
// ⚠ The engine's own rank for it is TODAY the default 8 ("unavailable"), which is false for a
// fluent answer in the wrong language; rank 1 arrives with the engine pack. That is the engine's
// half and it does not reach this map: nothing here reads the rank.
"off_target_lang": {"wrong_language", StepAttention},
}
// NoteCode maps one engine reason onto what the wire carries.
func NoteCode(reason string) (code, step string) {
if n, ok := notes[reason]; ok {
return n.code, n.step
}
// A reason this build has never heard of gets the quiet step: the client draws a neutral phrase
// for a code it does not know, and calling an unknown thing urgent is the one reading that is
// certainly wrong.
return NoteCodeUnspecified, StepGlance
}