From 78b7b3480b1709e06e4fdb19f5577462905f716c Mon Sep 17 00:00:00 2001 From: "Claude (backend session)" Date: Sat, 1 Aug 2026 17:36:43 +0300 Subject: [PATCH] Land bank fix-pack as D39.75: head finality in data, honest batch comment, classify fail-loud, neuter mutation tests, series fixture, tokenizer consolidation, soft-sign rollback kept with nails --- backend/internal/checks/voice.go | 12 ++ backend/internal/config/pipeline.go | 7 + .../config/terminology_classify_test.go | 117 ++++++++++++ backend/internal/lang/data/script-series.txt | 10 + backend/internal/lang/data/target-ru.txt | 7 + backend/internal/lang/embedded.go | 2 +- backend/internal/lang/script.go | 79 +++++++- backend/internal/lang/script_test.go | 49 +++++ backend/internal/lang/stemmer.go | 26 +-- backend/internal/lang/stemmer_test.go | 15 ++ backend/internal/membank/memdecl_test.go | 7 + backend/internal/membank/memneuter_test.go | 50 +++++ backend/internal/pipeline/terminologist.go | 10 + .../pipeline/testdata/golden/capture.golden | 172 +++++++++--------- backend/internal/terminology/series_test.go | 92 ++++++++++ backend/internal/terminology/terminology.go | 21 ++- .../testdata/bankfull-surfaces.txt | 155 ++++++++++++++++ backend/internal/text/runes.go | 19 +- backend/internal/text/tokenize_test.go | 31 ++++ docs/PROGRESS.md | 14 +- docs/README.md | 8 +- docs/architecture/05-decisions-log.md | 4 +- .../BACKEND_BANK_FIXPACK_SESSION_PROMPT.md | 8 +- .../reports/BANK_FIXPACK_2026-08-02.md | 53 ++++++ 24 files changed, 835 insertions(+), 133 deletions(-) create mode 100644 backend/internal/config/terminology_classify_test.go create mode 100644 backend/internal/lang/data/script-series.txt create mode 100644 backend/internal/membank/memneuter_test.go create mode 100644 backend/internal/terminology/testdata/bankfull-surfaces.txt create mode 100644 backend/internal/text/tokenize_test.go rename docs/{ => archive/prompts}/BACKEND_BANK_FIXPACK_SESSION_PROMPT.md (89%) create mode 100644 docs/archive/reports/BANK_FIXPACK_2026-08-02.md diff --git a/backend/internal/checks/voice.go b/backend/internal/checks/voice.go index 0f131d58..9158dcab 100644 --- a/backend/internal/checks/voice.go +++ b/backend/internal/checks/voice.go @@ -365,6 +365,18 @@ func (c *Checkers) countSourceReplies(source string) int { return n } +// DECL-AWARE STEMMING IS DELIBERATELY NOT WIRED INTO THESE MATCHERS (fix-pack §е, D39.71). The bank's +// stemmer (lang.TargetStemmer) is the right tool for the POST-CHECK, where a miss is uniformly the safe +// direction (a false omission flag) — and where character NAMES already arrive declension-aware, because +// Forms carries the base dst plus its enumerated decl forms (the same decl.forms[] the bank uses). Stemming +// on top of that would only add stem-collision MIS-ATTRIBUTION risk to a checker whose whole doctrine is +// precision over recall. For SelfRef/NG (single surfaces) the accept-biased stemmer is not uniformly safe +// either: stemming SelfRef would suppress a false self-ref flag on a declined designation (safe), but stemming +// an NG lexeme would EXPAND the forbidden-word match (a recall boost that manufactures noise) — opposite +// directions on one checker. A declension-aware SelfRef/NG belongs in the DRIVER (enumerate forms from the +// seed, as Forms already are), not in a match-time stemmer whose safe direction flips per axis. Recorded here +// so the decision stays closed rather than silently re-opened. + // wholeWord reports whether form occurs in the normalized haystack bounded by non-word runes. func wholeWord(hay []rune, form string) bool { f := []rune(form) diff --git a/backend/internal/config/pipeline.go b/backend/internal/config/pipeline.go index e69f71e5..4ff42331 100644 --- a/backend/internal/config/pipeline.go +++ b/backend/internal/config/pipeline.go @@ -1088,6 +1088,13 @@ func LoadPipeline(path string, models *Models, pair string, labels []string) (*P } } } + } else if tg.ClassifyTypes { + // The classifier is a PHASE of the terminology gate — loadClassifierTemplate is reached only through + // loadTerminologyTemplate, and runClassifier only through runTerminologist, both of which return early + // when the gate is off. So classify_types under a DISABLED gate never fires and, until now, was never + // validated either: a silent no-op, the "gate that cannot fire" class this loader rejects everywhere + // else. Fail loud instead of running with a phase the operator believes is on. + bad("gates.terminology.classify_types is on but gates.terminology.enabled is false — the classifier is a phase of the terminology gate and never runs on its own; enable the gate, or drop classify_types") } if len(problems) > 0 { return nil, fmt.Errorf("config %s:\n - %s", path, strings.Join(problems, "\n - ")) diff --git a/backend/internal/config/terminology_classify_test.go b/backend/internal/config/terminology_classify_test.go new file mode 100644 index 00000000..cc4ae961 --- /dev/null +++ b/backend/internal/config/terminology_classify_test.go @@ -0,0 +1,117 @@ +package config + +import ( + "path/filepath" + "strings" + "testing" +) + +// terminology_classify_test.go pins the fix-pack §г config branches for the §2 classifier PHASE. The +// classifier is a phase of the terminology gate, so — like the render phase — an enabled phase must be able to +// fire (a resolvable non-additive model, a positive budget, its own prompt), and classify_types under a +// DISABLED gate is a loud error rather than the silent no-op it was (runClassifier is reached only through the +// enabled gate). + +// termProject writes a zh-ru pack with a terminologist prompt and, optionally, a classifier prompt. +func termProject(t *testing.T, withClassifier bool) string { + t.Helper() + dir := promptProject(t, "zh-ru") + body := "sys\n---USER---\n{{text}}" + writeTmp(t, filepath.Join(dir, "prompts", "zh-ru", "terminologist.md"), body) + if withClassifier { + writeTmp(t, filepath.Join(dir, "prompts", "zh-ru", "classifier.md"), body) + } + return dir +} + +// loadClassify assembles a config with the given terminology-gate body and loads it for zh-ru. +func loadClassify(t *testing.T, withClassifier bool, gateBody string) error { + t.Helper() + dir := termProject(t, withClassifier) + cfg := draftOnlyConfig(t, dir, gateBody) + _, err := LoadPipeline(cfg, miniModels(t), "zh-ru", nil) + return err +} + +const validClassifyGate = ` +gates: + terminology: + enabled: true + model: fake + budget_usd: 1 + target_script: Cyrillic + classify_types: true + classify_budget_usd: 0.5 +` + +func TestClassifyValidConfigLoads(t *testing.T) { + if err := loadClassify(t, true, validClassifyGate); err != nil { + t.Fatalf("a well-formed classify config must load: %v", err) + } +} + +func TestClassifyMissingPromptFailsLoud(t *testing.T) { + err := loadClassify(t, false, validClassifyGate) // no classifier.md written + if err == nil { + t.Fatal("classify_types on with no classifier.md must fail loud") + } + for _, want := range []string{"classify_types", filepath.Join("zh-ru", "classifier.md")} { + if !strings.Contains(err.Error(), want) { + t.Errorf("error must name %q, got: %v", want, err) + } + } +} + +func TestClassifyBudgetMustBePositive(t *testing.T) { + body := ` +gates: + terminology: + enabled: true + model: fake + budget_usd: 1 + target_script: Cyrillic + classify_types: true + classify_budget_usd: 0 +` + err := loadClassify(t, true, body) + if err == nil || !strings.Contains(err.Error(), "classify_budget_usd") { + t.Fatalf("a zero classify budget must fail loud (a phase that can never spend), got: %v", err) + } +} + +func TestClassifyModelMustExist(t *testing.T) { + body := ` +gates: + terminology: + enabled: true + model: fake + budget_usd: 1 + target_script: Cyrillic + classify_types: true + classify_budget_usd: 0.5 + classify_model: ghost +` + err := loadClassify(t, true, body) + if err == nil || !strings.Contains(err.Error(), "classify_model") { + t.Fatalf("an unknown classify_model must fail loud, got: %v", err) + } +} + +// The fix-pack §г hygiene: classify_types under a disabled gate was a silent no-op — now a loud error. +func TestClassifyTypesRequiresEnabledGate(t *testing.T) { + body := ` +gates: + terminology: + enabled: false + classify_types: true +` + err := loadClassify(t, true, body) + if err == nil { + t.Fatal("classify_types with the gate disabled must fail loud, not run as a silent no-op") + } + for _, want := range []string{"classify_types", "enabled"} { + if !strings.Contains(err.Error(), want) { + t.Errorf("error must name %q, got: %v", want, err) + } + } +} diff --git a/backend/internal/lang/data/script-series.txt b/backend/internal/lang/data/script-series.txt new file mode 100644 index 00000000..50260acf --- /dev/null +++ b/backend/internal/lang/data/script-series.txt @@ -0,0 +1,10 @@ +# Series HEAD position per writing SCRIPT (bank-quality §1, fix-pack §б, D39.71): where a dense script's +# rune-morpheme SERIES puts the shared generic HEAD — LAST (modifier-head, the CJK norm: 甲等/乙等/丙等 share +# the trailing 等) or FIRST. Read by lang.SeriesMorphology so the terminologist co-batches a grade/rank series +# head-consistently with NO Go branch; head-finality was a hardcoded Go constant before this. A dense script +# (one named in cjkScriptNames) with no row here defaults to head-FINAL, the CJK-class default — so declaring +# a new dense script head-INITIAL is one data row, not a Go edit. `head_positionscriptnamefinal|initial`. +head_position han final +head_position hiragana final +head_position katakana final +head_position hangul final diff --git a/backend/internal/lang/data/target-ru.txt b/backend/internal/lang/data/target-ru.txt index 641e34df..423347bb 100644 --- a/backend/internal/lang/data/target-ru.txt +++ b/backend/internal/lang/data/target-ru.txt @@ -228,3 +228,10 @@ decl_suffix е decl_suffix ы decl_suffix и decl_suffix о +# NB (fix-pack §а, D39.71): the bare SOFT SIGN «ь» is deliberately NOT a decl_suffix. It would close the +# Palladius soft-sign case («Фан Юаню»~«Фан Юань» via the stem «юан»), but that strip is byte-identical in +# shape to a HARMFUL collision — «Синь»/«Линь» (top zh surnames) collapse onto «син»/«лин», the stems of the +# common words «синий»/«линия», which fires a spurious Bank.SpoilerLeaks false-positive (the noise direction). +# «юань»→«юан» and «синь»→«син» are the same 4-rune-soft-sign→3-rune-stem shape, so no min-stem rule separates +# them. The soft-sign nominative gap needs seed decl-form enumeration or an anchor-gated match, not this global +# suffix — see the fix-pack report §а ping. diff --git a/backend/internal/lang/embedded.go b/backend/internal/lang/embedded.go index 80524914..0f495bfd 100644 --- a/backend/internal/lang/embedded.go +++ b/backend/internal/lang/embedded.go @@ -19,7 +19,7 @@ import ( // data/algorithm boundary holds; embedded so it is available without a book's langpack_root. Each file is // SECTIONED per language/target, so adding a language = add a section, removing one = delete its section. -//go:embed data/cjk-section.txt data/refusal.txt data/injection.txt data/sentence-abbrev.txt data/target-ru.txt data/terminator.txt data/lang-script.txt +//go:embed data/cjk-section.txt data/refusal.txt data/injection.txt data/sentence-abbrev.txt data/target-ru.txt data/terminator.txt data/lang-script.txt data/script-series.txt var embeddedFS embed.FS // embedAlgoVersion tags the embedded-data content-hash RECIPE (like packAlgoVersion tags the pack's). Bump diff --git a/backend/internal/lang/script.go b/backend/internal/lang/script.go index 53b928a5..d7434f91 100644 --- a/backend/internal/lang/script.go +++ b/backend/internal/lang/script.go @@ -83,16 +83,81 @@ func IsCJKScriptLang(lng string) bool { return false } +var ( + scriptSeriesOnce sync.Once + scriptHeadFinal map[string]bool // script name → head-final (present only for scripts with a data row) +) + +// loadScriptSeries parses the embedded script-series table once (script name → head-final flag). +func loadScriptSeries() { + scriptSeriesOnce.Do(func() { + m, err := parseScriptHeadFinal(mustEmbed("data/script-series.txt")) + if err != nil { + panic(fmt.Sprintf("lang: embedded script-series.txt is corrupt: %v", err)) + } + scriptHeadFinal = m + }) +} + +// parseScriptHeadFinal reads `head_positionscriptnamefinal|initial` rows into script name → +// head-final. Pure, so a test can drive it with a synthetic table and prove a new script's head direction is +// DATA, not a Go edit. An unknown category or position value is a corrupt-data error, never silent. +func parseScriptHeadFinal(b []byte) (map[string]bool, error) { + out := map[string]bool{} + for i, raw := range strings.Split(string(b), "\n") { + t := strings.TrimSpace(strings.TrimRight(raw, "\r")) + if t == "" || strings.HasPrefix(t, "#") { + continue + } + f := strings.Split(t, "\t") + if len(f) != 3 || strings.TrimSpace(f[0]) != "head_position" { + return nil, fmt.Errorf("line %d: want `head_positionscriptnamefinal|initial`, got %q", i+1, t) + } + name := strings.TrimSpace(f[1]) + switch strings.TrimSpace(f[2]) { + case "final": + out[name] = true + case "initial": + out[name] = false + default: + return nil, fmt.Errorf("line %d: position must be final|initial, got %q", i+1, f[2]) + } + } + return out, nil +} + +// SeriesHeadFinal reports whether a dense SCRIPT forms head-FINAL series (the shared generic head is the +// trailing rune(s)). It is DECLARED data (data/script-series.txt); a dense script with no row defaults to +// head-final, the CJK modifier-head norm — so a dense script that co-batches head-INITIALLY is added by a +// data row, with no Go edit. Exported so the data-drive is testable without routing through a source language. +func SeriesHeadFinal(scriptName string) bool { + loadScriptSeries() + if hf, ok := scriptHeadFinal[scriptName]; ok { + return hf + } + return true +} + // SeriesMorphology reports whether a SOURCE language forms rune-morpheme SERIES the terminologist should // co-batch (甲等/乙等/丙等 — grades sharing the head 等), and whether that shared head is final. Series apply // to DENSE (CJK) scripts, where one rune ≈ one morpheme so a single-rune difference is a real minimal pair; -// in an alphabetic source care/core differ in one letter by coincidence, so the channel stays off. CJK noun -// compounds are modifier-HEAD, so the generic word is the trailing rune(s): head-final. A linguistic constant -// like cjkScriptNames, not a pair/book branch — the one place to revisit if a non-head-final dense script is -// ever added. +// in an alphabetic source care/core differ in one letter by coincidence, so the channel stays off. Head +// finality is no longer a Go constant: it is resolved from the language's dense script(s) via SeriesHeadFinal +// (data), so a dense non-head-final script needs no Go edit — headFinal is true only when EVERY dense script +// of the language is head-final (they agree for zh/ja/ko; any head-initial dense script flips it). func SeriesMorphology(lng string) (enabled, headFinal bool) { - if IsCJKScriptLang(lng) { - return true, true + loadLangScripts() + headFinal = true + for name := range langScriptBy[strings.ToLower(strings.TrimSpace(lng))] { + if cjkScriptNames[name] { + enabled = true + if !SeriesHeadFinal(name) { + headFinal = false + } + } } - return false, false + if !enabled { + return false, false + } + return true, headFinal } diff --git a/backend/internal/lang/script_test.go b/backend/internal/lang/script_test.go index 38e434e0..e4d92c66 100644 --- a/backend/internal/lang/script_test.go +++ b/backend/internal/lang/script_test.go @@ -24,3 +24,52 @@ func TestSecondPairDryRun(t *testing.T) { t.Fatal("ko/en must resolve scripts — these are the fixed blind spots") } } + +// TestSeriesMorphologyUnchanged pins that moving head-finality into data (fix-pack §б) reproduces the prior +// Go-constant behaviour: every dense source co-batches head-final, every alphabetic source has the channel off. +func TestSeriesMorphologyUnchanged(t *testing.T) { + for _, tc := range []struct { + lang string + enabled, headFinal bool + }{ + {"zh", true, true}, {"ja", true, true}, {"ko", true, true}, + {"en", false, false}, {"ru", false, false}, {"unknown", false, false}, + } { + en, hf := SeriesMorphology(tc.lang) + if en != tc.enabled || hf != tc.headFinal { + t.Errorf("SeriesMorphology(%q) = (%v,%v), want (%v,%v)", tc.lang, en, hf, tc.enabled, tc.headFinal) + } + } + // The real embedded data must declare han head-final, or zh would silently flip. + if !SeriesHeadFinal("han") { + t.Fatal("data/script-series.txt must declare han head-final") + } +} + +// TestSeriesHeadFinalIsDataDriven is the fix-pack §б generality proof: a dense script's head direction comes +// from DATA, so declaring a dense NON-head-final script is a data row, not a Go edit. Driven through the pure +// parser with a synthetic table so it needs no real embedded script. +func TestSeriesHeadFinalIsDataDriven(t *testing.T) { + m, err := parseScriptHeadFinal([]byte( + "# synthetic\nhead_position\ttengwar\tinitial\nhead_position\tcirth\tfinal\n")) + if err != nil { + t.Fatalf("valid table must parse: %v", err) + } + if m["tengwar"] { + t.Fatal("a script declared `initial` must resolve head-INITIAL from data with no Go edit") + } + if !m["cirth"] { + t.Fatal("a script declared `final` must resolve head-final") + } + // An unlisted script defaults to head-final (the CJK modifier-head norm) through the exported resolver. + if !SeriesHeadFinal("no-such-script-xyz") { + t.Fatal("an unlisted script must default to head-final") + } + // Corrupt data is loud, never silent. + if _, err := parseScriptHeadFinal([]byte("head_position\than\tsideways\n")); err == nil { + t.Fatal("an invalid head position must be a parse error") + } + if _, err := parseScriptHeadFinal([]byte("wrong_cat\than\tfinal\n")); err == nil { + t.Fatal("an unknown category must be a parse error") + } +} diff --git a/backend/internal/lang/stemmer.go b/backend/internal/lang/stemmer.go index beec1f14..aa028ca9 100644 --- a/backend/internal/lang/stemmer.go +++ b/backend/internal/lang/stemmer.go @@ -3,7 +3,8 @@ package lang import ( "sort" "strings" - "unicode" + + "textmachine/backend/internal/text" ) // stemmer.go: the conservative TARGET stemmer (bank-quality §3, primitive C). It strips one inflectional @@ -79,23 +80,6 @@ func (s TargetStemmer) SameStem(a, b string) bool { // TokenizeWords splits a target string into its whole words (maximal letter runs), lower-cased. Script- // generic (any Unicode letter), so it needs no per-language branch. Used to match a declined multi-word term -// head against the output. -func TokenizeWords(s string) []string { - var out []string - var cur []rune - flush := func() { - if len(cur) > 0 { - out = append(out, string(cur)) - cur = cur[:0] - } - } - for _, r := range strings.ToLower(s) { - if unicode.IsLetter(r) { - cur = append(cur, r) - continue - } - flush() - } - flush() - return out -} +// head against the output. It delegates to text.TokenizeLetters — the ONE tokenizer, shared with the target +// lexical checkers' TokenizeScript, so a stress-accented output word no longer splits mid-word here either. +func TokenizeWords(s string) []string { return text.TokenizeLetters(s) } diff --git a/backend/internal/lang/stemmer_test.go b/backend/internal/lang/stemmer_test.go index 24eb7848..40c88999 100644 --- a/backend/internal/lang/stemmer_test.go +++ b/backend/internal/lang/stemmer_test.go @@ -30,6 +30,21 @@ func TestTargetStemmerRuDeclension(t *testing.T) { } } +// TestSoftSignNamesDoNotCollideWithCommonWords is the fix-pack §а REGRESSION GUARD: a bare «ь» decl_suffix was +// weighed to close the Palladius soft-sign case («Фан Юаню»~«Фан Юань») and REJECTED, because stripping «ь» +// collapses top zh surnames onto common-adjective stems — «Синь/Линь» (辛/信/新, 林) → «син/лин» = «синий/линия». +// That fires a spurious Bank.SpoilerLeaks false-positive (the noise direction). This guard fails loudly if +// «ь» is ever re-added to the registry, so the collision cannot silently return. +func TestSoftSignNamesDoNotCollideWithCommonWords(t *testing.T) { + s := NewTargetStemmer(TargetChecksFor("ru")) + for _, pair := range [][2]string{{"Синь", "синий"}, {"Синь", "синим"}, {"Линь", "линию"}, {"Тень", "теней"}} { + if s.SameStem(pair[0], pair[1]) { + t.Errorf("«%s» (a soft-sign name) must NOT share a stem with the common word «%s» — a bare «ь» decl_suffix was re-added and re-introduced the collision (%q vs %q)", + pair[0], pair[1], s.Stem(pair[0]), s.Stem(pair[1])) + } + } +} + func TestTargetStemmerInertWithoutRegistry(t *testing.T) { var s TargetStemmer // zero value = a target with no decl_suffix data if s.Enabled() { diff --git a/backend/internal/membank/memdecl_test.go b/backend/internal/membank/memdecl_test.go index d92700f1..c564e84b 100644 --- a/backend/internal/membank/memdecl_test.go +++ b/backend/internal/membank/memdecl_test.go @@ -30,6 +30,13 @@ func TestDstFormPresentAcceptsDeclension(t *testing.T) { if !present(t, multi, "они стояли у горы Цинмао", s) { t.Fatal("a multi-word term with the head inflected must be accepted (горы Цинмао ~ гора Цинмао)") } + // Fix-pack §а: the Palladius soft-sign case («Фан Юаню»~«Фан Юань») is NOT closed here — a bare «ь» + // decl_suffix that would close it also collapses «Синь»/«Линь» onto «синий»/«линия» (a SpoilerLeaks + // false-positive), so it was rejected. The soft-sign gap is left for seed decl-enumeration; this asserts + // the current honest behaviour (an oblique soft-sign name is NOT matched) so a naive re-add is visible. + if present(t, &entry{dst: "Синь"}, "небо было синим", s) { + t.Fatal("«Синь» must NOT match «синим» — a bare «ь» decl_suffix re-introduced the collision (fix-pack §а)") + } } // TestDstFormPresentInertWithoutStemmer guards the generality path: a target with no decl_suffix registry diff --git a/backend/internal/membank/memneuter_test.go b/backend/internal/membank/memneuter_test.go new file mode 100644 index 00000000..a7c3301c --- /dev/null +++ b/backend/internal/membank/memneuter_test.go @@ -0,0 +1,50 @@ +package membank + +import ( + "testing" + + "textmachine/backend/internal/store" +) + +// memneuter_test.go pins the fix-pack §д money contract of the §3 neuter directive, on the memvoice pin's +// pattern: gender:neuter now RENDERS a directive where the same row rendered nothing before, so it MUST move +// memory_version (a loud, re-pinnable --resnapshot); and the tag respects the mined scope exactly as the +// pack-19 voice fold does, so a mined-only neuter row never re-bills the draft wave that cannot see it. +// +// (The neuter-directive-v1 marker itself is not black-box-isolable from the per-row gender field, which is +// already folded — both move the hash for a neuter row, and no single code version can hold one constant +// while toggling the other. These tests pin the OBSERVABLE contract; the marker exists to carry the RENDER +// revision across the deploy that introduced neuter rendering, which the scope test's line-ordering guards.) + +func TestNeuterRowMovesTheBankVersion(t *testing.T) { + base := term("元气", "изначальная ци") + plain := ComputeVersionScopedIn(BankInput{Rows: []store.GlossaryEntry{base}}, false, false) + neut := base + neut.Gender = "neuter" + if withNeuter := ComputeVersionScopedIn(BankInput{Rows: []store.GlossaryEntry{neut}}, false, false); plain == withNeuter { + t.Fatal("a gender:neuter row left the bank version unchanged — it now renders a directive the snapshot cannot see, a silent re-payment") + } +} + +// TestNeuterFoldRespectsTheMinedScope mirrors TestVoiceFoldRespectsTheMinedScopeBeforeCountingIt: the tag +// condition is evaluated only AFTER the mined-exclusion filter, so a mined-only neuter row folds into the +// enriched (edit-wave) version and NEVER the base (draft-wave) one — the draft wave never renders a mined +// row's directive, so it must not re-pay for one. +func TestNeuterFoldRespectsTheMinedScope(t *testing.T) { + minedNeuter := term("元气", "изначальная ци") + minedNeuter.Source, minedNeuter.Gender = "mined", "neuter" + minedPlain := minedNeuter + minedPlain.Gender = "" + withNeuter := BankInput{Rows: []store.GlossaryEntry{term("方源", "Фан Юань"), minedNeuter}} + plain := BankInput{Rows: []store.GlossaryEntry{term("方源", "Фан Юань"), minedPlain}} + + // BASE: the mined neuter row is out of scope, so neither its gender field nor the tag folds → identical. + if got, want := ComputeVersionScopedIn(withNeuter, false, true), ComputeVersionScopedIn(plain, false, true); got != want { + t.Fatalf("a mined-only neuter row moved the BASE version — the draft wave would re-pay for a directive it never renders\n got %s\n want %s", got, want) + } + // ENRICHED: the mined neuter row IS in scope, so the version moves — a loud re-snapshot for the wire the + // edit wave sees. + if ComputeVersionScopedIn(withNeuter, false, false) == ComputeVersionScopedIn(plain, false, false) { + t.Fatal("a mined neuter row did NOT move the enriched version — the edit wave would miss the directive") + } +} diff --git a/backend/internal/pipeline/terminologist.go b/backend/internal/pipeline/terminologist.go index a88ac068..4758ef27 100644 --- a/backend/internal/pipeline/terminologist.go +++ b/backend/internal/pipeline/terminologist.go @@ -269,6 +269,16 @@ func (r *Runner) runTerminologist(ctx context.Context, snapID string, cands []te seriesID := terminology.DetectSeries(cands, terminology.SeriesParams{Enabled: sEnabled, HeadFinal: headFinal}) batches := terminology.Batch(cands, batchRunes, seriesID) res.Batches = len(batches) + // §1 keeps a series (or a lone candidate) WHOLE even past the budget — splitting a series would defeat the + // one-head co-batching it exists for — so a co-batched grade set or an evidence-heavy single term can render + // over the cap. That is deliberate but NOT silent: an over-cap unit strains the model's output ceiling (the + // cap-8000 mine), so name it while the run can still be watched. + for i, b := range batches { + if br := terminology.BatchRunes(b); br > batchRunes { + r.Log.WarnContext(ctx, "terminology: a batch renders OVER the size budget and is sent WHOLE (a series is co-batched by design, §1; a lone candidate cannot be split) — watch the model's output cap on this call", + "book", r.Book.BookID, "batch", i, "terms", len(b), "batch_runes", br, "budget_runes", batchRunes) + } + } // The signed bank is read ONCE for the whole role, not per batch: it is the same law for every batch. canon := r.approvedNeighbours() diff --git a/backend/internal/pipeline/testdata/golden/capture.golden b/backend/internal/pipeline/testdata/golden/capture.golden index 7fdc2892..e4ef6a34 100644 --- a/backend/internal/pipeline/testdata/golden/capture.golden +++ b/backend/internal/pipeline/testdata/golden/capture.golden @@ -1,8 +1,8 @@ ==== run 1 (fresh) ==== -snapshot_draft: dc7572ca82f1898969220aecbb5727c36b9a42486ee70237ae8f95b8fe9ebce4 -snapshot_draft_payload: {"brief_hash":"c2021b1b5e6c20f6084e425b9a70bdc5f682801be347a57eb1c591deb8cab0df","chunker_version":"chunker-v6-generic-heading+srcabbrev+u15.0.0","estimator_version":"estimator-v0+u15.0.0","max_tokens_policy":"maxtok-v1-double-per-attempt","classifier_version":"classify-v2-refusal+srcscript-echo015+loop+u15.0.0","pipeline_core":"C1","max_output_ratio":2,"min_max_tokens":512,"context_assembly":{"glossary_injection":"selective","glossary_token_budget":800,"cache_ttl":""},"segmentation":{"draft_budget_out":1797,"edit_ceiling_out":3200,"fertility_cjk":1.1978,"fertility_other":0.3852},"render_format_version":"renderfmt-v3-draft-gender+editor-src2dst+dc3-gender","embedded_version":"embed-v1-37bf9699835f","memory_version":"bd451833d4ebb8a0ca07caf8deda76f027e1f42b78654987a22d215acf955eb7","postcheck_gate":false,"coverage":{"enabled":false},"style_check_version":"cheapgate-v6-target-tokenizer-combmark+u15.0.0","sanitizer":{"enabled":true,"version":"sanitizer-v7-hangul-leak"},"stages":[{"name":"draft","role":"translator","model":"fake-model","prompt_version":"v-golden","prompt_sha256":"26d0245a56d180eafd200ebe5311aab2488142c4a98dcddee21ea457814aa21a","temperature":0.3,"reasoning":"off","capability":{"Budget":"max_tokens","Temp":"send","TempValue":0,"Reasoning":{"Control":"none","OffEffort":"","OffExtraBody":null,"OnExtraBody":null},"MinMaxTokens":4000},"escalate_to":"fake-fallback","escalate_capability":{"Budget":"max_tokens","Temp":"send","TempValue":0,"Reasoning":{"Control":"none","OffEffort":"","OffExtraBody":null,"OnExtraBody":null},"MinMaxTokens":6000}}]} -snapshot_edit: 6799e44bbf02e21419447613ec3915f9f47c61f2746b94c1fc898ec9f5aa70bf -snapshot_edit_payload: {"brief_hash":"c2021b1b5e6c20f6084e425b9a70bdc5f682801be347a57eb1c591deb8cab0df","chunker_version":"chunker-v6-generic-heading+srcabbrev+u15.0.0","estimator_version":"estimator-v0+u15.0.0","max_tokens_policy":"maxtok-v1-double-per-attempt","classifier_version":"classify-v2-refusal+srcscript-echo015+loop+u15.0.0","pipeline_core":"C1","max_output_ratio":2,"min_max_tokens":512,"context_assembly":{"glossary_injection":"selective","glossary_token_budget":800,"cache_ttl":""},"segmentation":{"draft_budget_out":1797,"edit_ceiling_out":3200,"fertility_cjk":1.1978,"fertility_other":0.3852},"render_format_version":"renderfmt-v3-draft-gender+editor-src2dst+dc3-gender","embedded_version":"embed-v1-37bf9699835f","memory_version":"0a48d6ed9409de09f514af91aebdd622637621c9dc9464e8898c238791033d91","postcheck_gate":false,"coverage":{"enabled":false},"style_check_version":"cheapgate-v6-target-tokenizer-combmark+u15.0.0","sanitizer":{"enabled":true,"version":"sanitizer-v7-hangul-leak"},"stages":[{"name":"edit","role":"editor","model":"fake-model","prompt_version":"v-golden","prompt_sha256":"6a8e7f71139d6bc09a95159d1cb895e05b56d2f2f7417c5e20a7073c517c79f6","temperature":0.4,"reasoning":"off","capability":{"Budget":"max_tokens","Temp":"send","TempValue":0,"Reasoning":{"Control":"none","OffEffort":"","OffExtraBody":null,"OnExtraBody":null},"MinMaxTokens":4000}}]} +snapshot_draft: 8e1f174e988d99786c177f152966aaf9a116e67b55bdf9329cefde45701bdcdf +snapshot_draft_payload: {"brief_hash":"c2021b1b5e6c20f6084e425b9a70bdc5f682801be347a57eb1c591deb8cab0df","chunker_version":"chunker-v6-generic-heading+srcabbrev+u15.0.0","estimator_version":"estimator-v0+u15.0.0","max_tokens_policy":"maxtok-v1-double-per-attempt","classifier_version":"classify-v2-refusal+srcscript-echo015+loop+u15.0.0","pipeline_core":"C1","max_output_ratio":2,"min_max_tokens":512,"context_assembly":{"glossary_injection":"selective","glossary_token_budget":800,"cache_ttl":""},"segmentation":{"draft_budget_out":1797,"edit_ceiling_out":3200,"fertility_cjk":1.1978,"fertility_other":0.3852},"render_format_version":"renderfmt-v3-draft-gender+editor-src2dst+dc3-gender","embedded_version":"embed-v1-3df4329e3b9e","memory_version":"bd451833d4ebb8a0ca07caf8deda76f027e1f42b78654987a22d215acf955eb7","postcheck_gate":false,"coverage":{"enabled":false},"style_check_version":"cheapgate-v6-target-tokenizer-combmark+u15.0.0","sanitizer":{"enabled":true,"version":"sanitizer-v7-hangul-leak"},"stages":[{"name":"draft","role":"translator","model":"fake-model","prompt_version":"v-golden","prompt_sha256":"26d0245a56d180eafd200ebe5311aab2488142c4a98dcddee21ea457814aa21a","temperature":0.3,"reasoning":"off","capability":{"Budget":"max_tokens","Temp":"send","TempValue":0,"Reasoning":{"Control":"none","OffEffort":"","OffExtraBody":null,"OnExtraBody":null},"MinMaxTokens":4000},"escalate_to":"fake-fallback","escalate_capability":{"Budget":"max_tokens","Temp":"send","TempValue":0,"Reasoning":{"Control":"none","OffEffort":"","OffExtraBody":null,"OnExtraBody":null},"MinMaxTokens":6000}}]} +snapshot_edit: b968f0f3ab875824174e199e5ea9135f4a799a54d394ee1b5f5899e92d97ce3c +snapshot_edit_payload: {"brief_hash":"c2021b1b5e6c20f6084e425b9a70bdc5f682801be347a57eb1c591deb8cab0df","chunker_version":"chunker-v6-generic-heading+srcabbrev+u15.0.0","estimator_version":"estimator-v0+u15.0.0","max_tokens_policy":"maxtok-v1-double-per-attempt","classifier_version":"classify-v2-refusal+srcscript-echo015+loop+u15.0.0","pipeline_core":"C1","max_output_ratio":2,"min_max_tokens":512,"context_assembly":{"glossary_injection":"selective","glossary_token_budget":800,"cache_ttl":""},"segmentation":{"draft_budget_out":1797,"edit_ceiling_out":3200,"fertility_cjk":1.1978,"fertility_other":0.3852},"render_format_version":"renderfmt-v3-draft-gender+editor-src2dst+dc3-gender","embedded_version":"embed-v1-3df4329e3b9e","memory_version":"0a48d6ed9409de09f514af91aebdd622637621c9dc9464e8898c238791033d91","postcheck_gate":false,"coverage":{"enabled":false},"style_check_version":"cheapgate-v6-target-tokenizer-combmark+u15.0.0","sanitizer":{"enabled":true,"version":"sanitizer-v7-hangul-leak"},"stages":[{"name":"edit","role":"editor","model":"fake-model","prompt_version":"v-golden","prompt_sha256":"6a8e7f71139d6bc09a95159d1cb895e05b56d2f2f7417c5e20a7073c517c79f6","temperature":0.4,"reasoning":"off","capability":{"Budget":"max_tokens","Temp":"send","TempValue":0,"Reasoning":{"Control":"none","OffEffort":"","OffExtraBody":null,"OnExtraBody":null},"MinMaxTokens":4000}}]} brief_hash: c2021b1b5e6c20f6084e425b9a70bdc5f682801be347a57eb1c591deb8cab0df memory_version: 0a48d6ed9409de09f514af91aebdd622637621c9dc9464e8898c238791033d91 base_memory_version: bd451833d4ebb8a0ca07caf8deda76f027e1f42b78654987a22d215acf955eb7 @@ -51,42 +51,42 @@ chunk ch8/0 disposition=flagged flag="sanitizer_stripped" final_text="Судзу stage=edit role=editor model=fake-model resume=false disp=flagged flag="sanitizer_stripped" attempts=1 escalated=false esc_model="" finish="stop" cum_usd=0.00182 detail="CJK leak in the ru output: 特产" stage_text="" recovered="Судзуки нашёл древний камень на каменном алтаре." -- chunk_status -- -ch1/0 draft snap_match=true content_hash=3e156226b9a74aa70c8a259c62e3d2fe68486a96d2178d31084c1e38a3256241 disp=ok flag="" attempts=1 final_hash=70bca3400d2d8d848af27845cb2b86f362cdcf7a3a73e2611c46ec05feb7c4c2 cost=0.00182 escalated=false esc_model="" detail="" -ch1/0 edit snap_match=true content_hash=5e79dd5cbd310ac01d71ca258002beca18db715dfb8b1a08907508e506595355 disp=ok flag="" attempts=1 final_hash=799b6e6419910a2e52dd4652f2bde9bee59fbdb92a849f291ed33a5e30410a15 cost=0.00182 escalated=false esc_model="" detail="" -ch1/1 draft snap_match=true content_hash=82fb7f985de6c07182e8c09a3ceaad94f2e63745b87024087e437fba762b8227 disp=ok flag="" attempts=1 final_hash=ad47812002136f356e16d4fe4d71b73fdf1f2fbbe4f6a2cecd696abb07b7502d cost=0.00182 escalated=false esc_model="" detail="" -ch2/0 draft snap_match=true content_hash=cfab1e09264a3a30aba95e7da1d6a269468c6aa0c1b334d9241e78bc266a3571 disp=ok flag="" attempts=1 final_hash=35d5f692c908d3c15b40757a167c0deb3067e90492a4490500b0a0ccf359c9e2 cost=0.00728 escalated=true esc_model="fake-fallback" detail="" -ch2/0 edit snap_match=true content_hash=55ffd839a4a26c03cf57e08000ab2c84b267e1429ab4a8e212d3a01fda757632 disp=ok flag="" attempts=1 final_hash=e3f8acb903b0dd5798636597412acadfcf03a968ff049a8604b1142dea5cded1 cost=0.00182 escalated=false esc_model="" detail="" +ch1/0 draft snap_match=true content_hash=3e156226b9a74aa70c8a259c62e3d2fe68486a96d2178d31084c1e38a3256241 disp=ok flag="" attempts=1 final_hash=58e9dbc06bfa575bbb3e1e108d565a8a48a2e07dbcc1b14633adf1b20c33bf21 cost=0.00182 escalated=false esc_model="" detail="" +ch1/0 edit snap_match=true content_hash=5e79dd5cbd310ac01d71ca258002beca18db715dfb8b1a08907508e506595355 disp=ok flag="" attempts=1 final_hash=366482169be171810b4478c7a10245779c921afdd0daf3c443a60495bf2ef737 cost=0.00182 escalated=false esc_model="" detail="" +ch1/1 draft snap_match=true content_hash=82fb7f985de6c07182e8c09a3ceaad94f2e63745b87024087e437fba762b8227 disp=ok flag="" attempts=1 final_hash=f62d390f0624ddcfbc27b048475b749d06b44ab91ade175985b162160235f6e7 cost=0.00182 escalated=false esc_model="" detail="" +ch2/0 draft snap_match=true content_hash=cfab1e09264a3a30aba95e7da1d6a269468c6aa0c1b334d9241e78bc266a3571 disp=ok flag="" attempts=1 final_hash=afc9e9ff7d6f1e4832365eefe29133bc8b8f70277fea9d8379d41a490c47f35c cost=0.00728 escalated=true esc_model="fake-fallback" detail="" +ch2/0 edit snap_match=true content_hash=55ffd839a4a26c03cf57e08000ab2c84b267e1429ab4a8e212d3a01fda757632 disp=ok flag="" attempts=1 final_hash=a57b6f62ebecf7cbdb04d8fce7679b68dedb4cb95dcac2605942aa3fdc864e05 cost=0.00182 escalated=false esc_model="" detail="" ch3/0 draft snap_match=true content_hash=21df44eb104900664bdaaeb2f716f51dcddbc81a71d845f6ba0f91c5bc4b2cfb disp=flagged flag="hard_refusal" attempts=1 final_hash= cost=0.00728 escalated=true esc_model="" detail="provider finish_reason=refusal" ch3/0 edit snap_match=true content_hash= disp=skipped flag="hard_refusal" attempts=0 final_hash= cost=0 escalated=false esc_model="" detail="skipped: a member draft chunk of this edit unit was flagged (hard_refusal)" -ch4/0 draft snap_match=true content_hash=7aabdcb89dea365c95363b2662144ca1a10b307d377f47a8e7cc19b5b872d287 disp=ok flag="" attempts=1 final_hash=c74a4b18be42dae5946fc9d33f1172e6be71a37bad7dc08076e5fba59316e858 cost=0.00182 escalated=false esc_model="" detail="" -ch4/0 edit snap_match=true content_hash=2854dfe1648b0cf0cbb683cb7ce94c5eedc9223b2ce20c99dc008c7add6317ef disp=ok flag="" attempts=1 final_hash=5f8f5737a4e02766f8500915341c8b8a8827f8a05b03d77a746a4a8bc48806eb cost=0.00182 escalated=false esc_model="" detail="" -ch5/0 draft snap_match=true content_hash=2ed0a97f39cc5c57c62c3324f4646357372f26e3c5bae1773f24edf9449fc013 disp=ok flag="" attempts=1 final_hash=407f023c09e54b0399f79cf59e893817065b28129a807c57b25cc796202d75e1 cost=0.00182 escalated=false esc_model="" detail="" -ch5/0 edit snap_match=true content_hash=b145d78e4d30a39cc4088a1ddb8d934234c65341c62c79545c2eff1e7ae0c3d3 disp=ok flag="" attempts=1 final_hash=2b722376f3691a64c38d9a8026f567dfce0dd0a5ca3d2355bd2a95e4807ee67c cost=0.00182 escalated=false esc_model="" detail="" -ch6/0 draft snap_match=true content_hash=5f396d299a7ec5b10f3c8591e54ce7d84c4d31b815e561b0dd5f56635aef5a71 disp=ok flag="" attempts=1 final_hash=afd2169fce4c9aaecdf7f3de1f1f87bc08d5b672d19bc1dc772991f8d3321d60 cost=0.00182 escalated=false esc_model="" detail="" +ch4/0 draft snap_match=true content_hash=7aabdcb89dea365c95363b2662144ca1a10b307d377f47a8e7cc19b5b872d287 disp=ok flag="" attempts=1 final_hash=95953ad9afa465ccc21cf6ae02556fcc2259593e51fb7d2a50cd24741cedf2d3 cost=0.00182 escalated=false esc_model="" detail="" +ch4/0 edit snap_match=true content_hash=2854dfe1648b0cf0cbb683cb7ce94c5eedc9223b2ce20c99dc008c7add6317ef disp=ok flag="" attempts=1 final_hash=51a33a39df9ac10b3de8111d4e6c14c97e52d694d1853d0564a49f081017ffdf cost=0.00182 escalated=false esc_model="" detail="" +ch5/0 draft snap_match=true content_hash=2ed0a97f39cc5c57c62c3324f4646357372f26e3c5bae1773f24edf9449fc013 disp=ok flag="" attempts=1 final_hash=df9779e5705e186c4e936d4a1cbdb0bc66d603954bc7dd5ca2b7a4b06c2c9d0d cost=0.00182 escalated=false esc_model="" detail="" +ch5/0 edit snap_match=true content_hash=b145d78e4d30a39cc4088a1ddb8d934234c65341c62c79545c2eff1e7ae0c3d3 disp=ok flag="" attempts=1 final_hash=d331964b915e5496a8c377189df897bdd5c3535e09f9ff4382e75eee7073c681 cost=0.00182 escalated=false esc_model="" detail="" +ch6/0 draft snap_match=true content_hash=5f396d299a7ec5b10f3c8591e54ce7d84c4d31b815e561b0dd5f56635aef5a71 disp=ok flag="" attempts=1 final_hash=639d1fdb559d75d1ff72f73d72820e9aac3ff58f6b867408058f6f0761184155 cost=0.00182 escalated=false esc_model="" detail="" ch6/0 edit snap_match=true content_hash=592353321cc8000e792e0897446e4dd4f88a0280b9368b62cbd5c82b0c433193 disp=flagged flag="sanitizer_defect" attempts=1 final_hash= cost=0.00182 escalated=false esc_model="" detail="leading service preamble: Вот перевод фрагмента:" -ch7/0 draft snap_match=true content_hash=15c3ebbd56bb19880202d35b0206357655da245343376492f7ceb61808c8a5be disp=ok flag="" attempts=1 final_hash=486facff54a77c86f44cd322200caf4cf982975eeda552fa6dfdee4aa8df0904 cost=0.00182 escalated=false esc_model="" detail="" -ch7/0 edit snap_match=true content_hash=0b2674ed9829b2e6526ff41a60dd280b6670c539476526bbe835d9bf7a6be4c4 disp=flagged flag="sanitizer_stripped" attempts=1 final_hash=tm-sanitized-v1:def877b81bcdc52e2525fdcaae04b8c64f632c7d6747b31df7f4c7f1ad176e73 cost=0.00182 escalated=false esc_model="" detail="markdown header in the output: ### Глава 7" -ch8/0 draft snap_match=true content_hash=4a420977cc34ce738b4978e6a1baf45d78d773a135070c3b7f8c723adfa57d7b disp=ok flag="" attempts=1 final_hash=070d13df73e028346b69abe69253a999f109d92b01f39f3dfada0772be50e185 cost=0.00182 escalated=false esc_model="" detail="" -ch8/0 edit snap_match=true content_hash=c851100c8d6785f77d68f8127472790ae9d0408b7fec44785d3ff9d46842c6f8 disp=flagged flag="sanitizer_stripped" attempts=1 final_hash=tm-sanitized-v1:5095da45458381184b8798287c934e133c39668fed86c50cce921a1cb7df7893 cost=0.00182 escalated=false esc_model="" detail="CJK leak in the ru output: 特产" +ch7/0 draft snap_match=true content_hash=15c3ebbd56bb19880202d35b0206357655da245343376492f7ceb61808c8a5be disp=ok flag="" attempts=1 final_hash=64cd75a5c6410ff1d7c1880dd0ca06c871e517e9cef2ca7923a01fbcd88821ef cost=0.00182 escalated=false esc_model="" detail="" +ch7/0 edit snap_match=true content_hash=0b2674ed9829b2e6526ff41a60dd280b6670c539476526bbe835d9bf7a6be4c4 disp=flagged flag="sanitizer_stripped" attempts=1 final_hash=tm-sanitized-v1:93ae4ce8992136692f558f136d12483aff728b1a0aa54388072335b4a0231267 cost=0.00182 escalated=false esc_model="" detail="markdown header in the output: ### Глава 7" +ch8/0 draft snap_match=true content_hash=4a420977cc34ce738b4978e6a1baf45d78d773a135070c3b7f8c723adfa57d7b disp=ok flag="" attempts=1 final_hash=e9c1dd1174091275bf8f2975f1238d53b0b557ea1fbef283729a36dfa36f5f0d cost=0.00182 escalated=false esc_model="" detail="" +ch8/0 edit snap_match=true content_hash=c851100c8d6785f77d68f8127472790ae9d0408b7fec44785d3ff9d46842c6f8 disp=flagged flag="sanitizer_stripped" attempts=1 final_hash=tm-sanitized-v1:e6a81289fc023f94c28749e97eff499ec5222ed807b4a3c10934dbbcfe4a0ffc cost=0.00182 escalated=false esc_model="" detail="CJK leak in the ru output: 特产" -- request_log (insertion order) -- -ch1/0 draft role=translator req=fake-model actual=fake-model hash=70bca3400d2d8d848af27845cb2b86f362cdcf7a3a73e2611c46ec05feb7c4c2 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch1/1 draft role=translator req=fake-model actual=fake-model hash=ad47812002136f356e16d4fe4d71b73fdf1f2fbbe4f6a2cecd696abb07b7502d tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch2/0 draft role=translator req=fake-model actual=fake-model hash=56b4390260a43c1ffd2ba17afb8c3fab16113992856bb960e62e11c4937e0028 tm_hit=0 ok=0 finish="stop" degraded="cjk_artifact" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch2/0 draft role=translator req=fake-fallback actual=fake-fallback hash=35d5f692c908d3c15b40757a167c0deb3067e90492a4490500b0a0ccf359c9e2 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.0054600000000000004 tokens=1000/200/0/500/0 err="" -ch3/0 draft role=translator req=fake-model actual=fake-model hash=9e6325591eca8bc28e34be1da64106704d9589193c991f6545b22e8ddbae3b54 tm_hit=0 ok=0 finish="refusal" degraded="hard_refusal" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch3/0 draft role=translator req=fake-fallback actual=fake-fallback hash=544c921f47e59718019cfff42782f590deba18352123ae0687ceade0130fe2bd tm_hit=0 ok=0 finish="refusal" degraded="hard_refusal" cost=0.0054600000000000004 tokens=1000/200/0/500/0 err="" -ch4/0 draft role=translator req=fake-model actual=fake-model hash=c74a4b18be42dae5946fc9d33f1172e6be71a37bad7dc08076e5fba59316e858 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch5/0 draft role=translator req=fake-model actual=fake-model hash=407f023c09e54b0399f79cf59e893817065b28129a807c57b25cc796202d75e1 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch6/0 draft role=translator req=fake-model actual=fake-model hash=afd2169fce4c9aaecdf7f3de1f1f87bc08d5b672d19bc1dc772991f8d3321d60 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch7/0 draft role=translator req=fake-model actual=fake-model hash=486facff54a77c86f44cd322200caf4cf982975eeda552fa6dfdee4aa8df0904 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch8/0 draft role=translator req=fake-model actual=fake-model hash=070d13df73e028346b69abe69253a999f109d92b01f39f3dfada0772be50e185 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch1/0 edit role=editor req=fake-model actual=fake-model hash=799b6e6419910a2e52dd4652f2bde9bee59fbdb92a849f291ed33a5e30410a15 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch2/0 edit role=editor req=fake-model actual=fake-model hash=e3f8acb903b0dd5798636597412acadfcf03a968ff049a8604b1142dea5cded1 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch4/0 edit role=editor req=fake-model actual=fake-model hash=5f8f5737a4e02766f8500915341c8b8a8827f8a05b03d77a746a4a8bc48806eb tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch5/0 edit role=editor req=fake-model actual=fake-model hash=2b722376f3691a64c38d9a8026f567dfce0dd0a5ca3d2355bd2a95e4807ee67c tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch6/0 edit role=editor req=fake-model actual=fake-model hash=806e0871ca091d6ad598412d1ed78c711c0bc19412b1c20599d25ea1a7d3dc0f tm_hit=0 ok=0 finish="stop" degraded="sanitizer_defect" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch7/0 edit role=editor req=fake-model actual=fake-model hash=c445cf04d138dd1ecfae1b805336f2c5cde333b575b64f75c10f1017e39ac498 tm_hit=0 ok=0 finish="stop" degraded="sanitizer_stripped" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch8/0 edit role=editor req=fake-model actual=fake-model hash=1a3bc5b91428e2cc31a965748ea10046bf6d9f420f8c8d3e79f9fd3da5b03f4f tm_hit=0 ok=0 finish="stop" degraded="sanitizer_stripped" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch1/0 draft role=translator req=fake-model actual=fake-model hash=58e9dbc06bfa575bbb3e1e108d565a8a48a2e07dbcc1b14633adf1b20c33bf21 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch1/1 draft role=translator req=fake-model actual=fake-model hash=f62d390f0624ddcfbc27b048475b749d06b44ab91ade175985b162160235f6e7 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch2/0 draft role=translator req=fake-model actual=fake-model hash=4c598abeab1852ea9cf980492f60a311382dd732e9c90e6146b3612bcc987cd2 tm_hit=0 ok=0 finish="stop" degraded="cjk_artifact" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch2/0 draft role=translator req=fake-fallback actual=fake-fallback hash=afc9e9ff7d6f1e4832365eefe29133bc8b8f70277fea9d8379d41a490c47f35c tm_hit=0 ok=1 finish="stop" degraded="" cost=0.0054600000000000004 tokens=1000/200/0/500/0 err="" +ch3/0 draft role=translator req=fake-model actual=fake-model hash=5806ff3fbd200810f0f01581eaf39a489ffb53fbd718fd696235a4bd4cdda367 tm_hit=0 ok=0 finish="refusal" degraded="hard_refusal" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch3/0 draft role=translator req=fake-fallback actual=fake-fallback hash=667725b23cd72e129132d47da0ff7db069ff4cece653b2518b7f9756600efaca tm_hit=0 ok=0 finish="refusal" degraded="hard_refusal" cost=0.0054600000000000004 tokens=1000/200/0/500/0 err="" +ch4/0 draft role=translator req=fake-model actual=fake-model hash=95953ad9afa465ccc21cf6ae02556fcc2259593e51fb7d2a50cd24741cedf2d3 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch5/0 draft role=translator req=fake-model actual=fake-model hash=df9779e5705e186c4e936d4a1cbdb0bc66d603954bc7dd5ca2b7a4b06c2c9d0d tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch6/0 draft role=translator req=fake-model actual=fake-model hash=639d1fdb559d75d1ff72f73d72820e9aac3ff58f6b867408058f6f0761184155 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch7/0 draft role=translator req=fake-model actual=fake-model hash=64cd75a5c6410ff1d7c1880dd0ca06c871e517e9cef2ca7923a01fbcd88821ef tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch8/0 draft role=translator req=fake-model actual=fake-model hash=e9c1dd1174091275bf8f2975f1238d53b0b557ea1fbef283729a36dfa36f5f0d tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch1/0 edit role=editor req=fake-model actual=fake-model hash=366482169be171810b4478c7a10245779c921afdd0daf3c443a60495bf2ef737 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch2/0 edit role=editor req=fake-model actual=fake-model hash=a57b6f62ebecf7cbdb04d8fce7679b68dedb4cb95dcac2605942aa3fdc864e05 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch4/0 edit role=editor req=fake-model actual=fake-model hash=51a33a39df9ac10b3de8111d4e6c14c97e52d694d1853d0564a49f081017ffdf tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch5/0 edit role=editor req=fake-model actual=fake-model hash=d331964b915e5496a8c377189df897bdd5c3535e09f9ff4382e75eee7073c681 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch6/0 edit role=editor req=fake-model actual=fake-model hash=1ec612b83d1bd0e657e40e189a988546406929290acbed8babcf230ac9ae592d tm_hit=0 ok=0 finish="stop" degraded="sanitizer_defect" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch7/0 edit role=editor req=fake-model actual=fake-model hash=4e4d6214cb7d85753935d8fc18a8a6fcf1f99bdef9ff4bc958a30749071af75b tm_hit=0 ok=0 finish="stop" degraded="sanitizer_stripped" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch8/0 edit role=editor req=fake-model actual=fake-model hash=fcc423a73253a774560ba8376f2c3c2db613eea3a74c3785772b56b3f36ce5ce tm_hit=0 ok=0 finish="stop" degraded="sanitizer_stripped" cost=0.00182 tokens=1000/200/0/500/0 err="" -- retrieval_state -- ch1/0 snap_match=true exact=3 sticky=0 ambiguous=0 spoiler=1 evicted=0 postcheck_miss=1 style_flags=0 trust_gated=0 injected_ids=["紋章\u001f\u001f0\u001f0","鈴木\u001f\u001f0\u001f0","魔法学院\u001f\u001f0\u001f0"] postcheck_detail=[{"src":"紋章","dst":"герб","disp":"confirmed"}] style_detail= trust_gate_detail= @@ -126,10 +126,10 @@ ch8/0 snap_match=true exact=1 sticky=0 ambiguous=0 spoiler=0 evicted=0 postcheck [16] {"max_tokens":4000,"messages":[{"role":"system","content":"Ты — монолингвальный редактор русского текста. Правь стиль, не меняя смысла. Книга: Золотая книга."},{"role":"system","content":"КАНОНИЧЕСКИЕ ПЕРЕВОДЫ имён и терминов (в черновике термин исходника слева ДОЛЖЕН быть передан именно указанной формой справа — приводи к ней любые расхождения, склоняя по контексту; не вводи иных вариантов и не меняй ничего другого):\n- 鈴木 → «Судзуки» (муж. — мужские родовые формы)"},{"role":"user","content":"Черновик перевода для редактуры: ЧЕРНОВИК ПЕРЕВОДА 9e65df37b751. МАРКДАУН-СЦЕНА Судзуки открыл седьмую дверь."}],"model":"fake-model","stream":false,"temperature":0.4} [17] {"max_tokens":4000,"messages":[{"role":"system","content":"Ты — монолингвальный редактор русского текста. Правь стиль, не меняя смысла. Книга: Золотая книга."},{"role":"system","content":"КАНОНИЧЕСКИЕ ПЕРЕВОДЫ имён и терминов (в черновике термин исходника слева ДОЛЖЕН быть передан именно указанной формой справа — приводи к ней любые расхождения, склоняя по контексту; не вводи иных вариантов и не меняй ничего другого):\n- 鈴木 → «Судзуки» (муж. — мужские родовые формы)"},{"role":"user","content":"Черновик перевода для редактуры: ЧЕРНОВИК ПЕРЕВОДА 2c858e3366d2. ИЕРОГЛИФ-СЦЕНА Судзуки нашёл камень."}],"model":"fake-model","stream":false,"temperature":0.4} ==== run 2 (resume) ==== -snapshot_draft: dc7572ca82f1898969220aecbb5727c36b9a42486ee70237ae8f95b8fe9ebce4 -snapshot_draft_payload: {"brief_hash":"c2021b1b5e6c20f6084e425b9a70bdc5f682801be347a57eb1c591deb8cab0df","chunker_version":"chunker-v6-generic-heading+srcabbrev+u15.0.0","estimator_version":"estimator-v0+u15.0.0","max_tokens_policy":"maxtok-v1-double-per-attempt","classifier_version":"classify-v2-refusal+srcscript-echo015+loop+u15.0.0","pipeline_core":"C1","max_output_ratio":2,"min_max_tokens":512,"context_assembly":{"glossary_injection":"selective","glossary_token_budget":800,"cache_ttl":""},"segmentation":{"draft_budget_out":1797,"edit_ceiling_out":3200,"fertility_cjk":1.1978,"fertility_other":0.3852},"render_format_version":"renderfmt-v3-draft-gender+editor-src2dst+dc3-gender","embedded_version":"embed-v1-37bf9699835f","memory_version":"bd451833d4ebb8a0ca07caf8deda76f027e1f42b78654987a22d215acf955eb7","postcheck_gate":false,"coverage":{"enabled":false},"style_check_version":"cheapgate-v6-target-tokenizer-combmark+u15.0.0","sanitizer":{"enabled":true,"version":"sanitizer-v7-hangul-leak"},"stages":[{"name":"draft","role":"translator","model":"fake-model","prompt_version":"v-golden","prompt_sha256":"26d0245a56d180eafd200ebe5311aab2488142c4a98dcddee21ea457814aa21a","temperature":0.3,"reasoning":"off","capability":{"Budget":"max_tokens","Temp":"send","TempValue":0,"Reasoning":{"Control":"none","OffEffort":"","OffExtraBody":null,"OnExtraBody":null},"MinMaxTokens":4000},"escalate_to":"fake-fallback","escalate_capability":{"Budget":"max_tokens","Temp":"send","TempValue":0,"Reasoning":{"Control":"none","OffEffort":"","OffExtraBody":null,"OnExtraBody":null},"MinMaxTokens":6000}}]} -snapshot_edit: 6799e44bbf02e21419447613ec3915f9f47c61f2746b94c1fc898ec9f5aa70bf -snapshot_edit_payload: {"brief_hash":"c2021b1b5e6c20f6084e425b9a70bdc5f682801be347a57eb1c591deb8cab0df","chunker_version":"chunker-v6-generic-heading+srcabbrev+u15.0.0","estimator_version":"estimator-v0+u15.0.0","max_tokens_policy":"maxtok-v1-double-per-attempt","classifier_version":"classify-v2-refusal+srcscript-echo015+loop+u15.0.0","pipeline_core":"C1","max_output_ratio":2,"min_max_tokens":512,"context_assembly":{"glossary_injection":"selective","glossary_token_budget":800,"cache_ttl":""},"segmentation":{"draft_budget_out":1797,"edit_ceiling_out":3200,"fertility_cjk":1.1978,"fertility_other":0.3852},"render_format_version":"renderfmt-v3-draft-gender+editor-src2dst+dc3-gender","embedded_version":"embed-v1-37bf9699835f","memory_version":"0a48d6ed9409de09f514af91aebdd622637621c9dc9464e8898c238791033d91","postcheck_gate":false,"coverage":{"enabled":false},"style_check_version":"cheapgate-v6-target-tokenizer-combmark+u15.0.0","sanitizer":{"enabled":true,"version":"sanitizer-v7-hangul-leak"},"stages":[{"name":"edit","role":"editor","model":"fake-model","prompt_version":"v-golden","prompt_sha256":"6a8e7f71139d6bc09a95159d1cb895e05b56d2f2f7417c5e20a7073c517c79f6","temperature":0.4,"reasoning":"off","capability":{"Budget":"max_tokens","Temp":"send","TempValue":0,"Reasoning":{"Control":"none","OffEffort":"","OffExtraBody":null,"OnExtraBody":null},"MinMaxTokens":4000}}]} +snapshot_draft: 8e1f174e988d99786c177f152966aaf9a116e67b55bdf9329cefde45701bdcdf +snapshot_draft_payload: {"brief_hash":"c2021b1b5e6c20f6084e425b9a70bdc5f682801be347a57eb1c591deb8cab0df","chunker_version":"chunker-v6-generic-heading+srcabbrev+u15.0.0","estimator_version":"estimator-v0+u15.0.0","max_tokens_policy":"maxtok-v1-double-per-attempt","classifier_version":"classify-v2-refusal+srcscript-echo015+loop+u15.0.0","pipeline_core":"C1","max_output_ratio":2,"min_max_tokens":512,"context_assembly":{"glossary_injection":"selective","glossary_token_budget":800,"cache_ttl":""},"segmentation":{"draft_budget_out":1797,"edit_ceiling_out":3200,"fertility_cjk":1.1978,"fertility_other":0.3852},"render_format_version":"renderfmt-v3-draft-gender+editor-src2dst+dc3-gender","embedded_version":"embed-v1-3df4329e3b9e","memory_version":"bd451833d4ebb8a0ca07caf8deda76f027e1f42b78654987a22d215acf955eb7","postcheck_gate":false,"coverage":{"enabled":false},"style_check_version":"cheapgate-v6-target-tokenizer-combmark+u15.0.0","sanitizer":{"enabled":true,"version":"sanitizer-v7-hangul-leak"},"stages":[{"name":"draft","role":"translator","model":"fake-model","prompt_version":"v-golden","prompt_sha256":"26d0245a56d180eafd200ebe5311aab2488142c4a98dcddee21ea457814aa21a","temperature":0.3,"reasoning":"off","capability":{"Budget":"max_tokens","Temp":"send","TempValue":0,"Reasoning":{"Control":"none","OffEffort":"","OffExtraBody":null,"OnExtraBody":null},"MinMaxTokens":4000},"escalate_to":"fake-fallback","escalate_capability":{"Budget":"max_tokens","Temp":"send","TempValue":0,"Reasoning":{"Control":"none","OffEffort":"","OffExtraBody":null,"OnExtraBody":null},"MinMaxTokens":6000}}]} +snapshot_edit: b968f0f3ab875824174e199e5ea9135f4a799a54d394ee1b5f5899e92d97ce3c +snapshot_edit_payload: {"brief_hash":"c2021b1b5e6c20f6084e425b9a70bdc5f682801be347a57eb1c591deb8cab0df","chunker_version":"chunker-v6-generic-heading+srcabbrev+u15.0.0","estimator_version":"estimator-v0+u15.0.0","max_tokens_policy":"maxtok-v1-double-per-attempt","classifier_version":"classify-v2-refusal+srcscript-echo015+loop+u15.0.0","pipeline_core":"C1","max_output_ratio":2,"min_max_tokens":512,"context_assembly":{"glossary_injection":"selective","glossary_token_budget":800,"cache_ttl":""},"segmentation":{"draft_budget_out":1797,"edit_ceiling_out":3200,"fertility_cjk":1.1978,"fertility_other":0.3852},"render_format_version":"renderfmt-v3-draft-gender+editor-src2dst+dc3-gender","embedded_version":"embed-v1-3df4329e3b9e","memory_version":"0a48d6ed9409de09f514af91aebdd622637621c9dc9464e8898c238791033d91","postcheck_gate":false,"coverage":{"enabled":false},"style_check_version":"cheapgate-v6-target-tokenizer-combmark+u15.0.0","sanitizer":{"enabled":true,"version":"sanitizer-v7-hangul-leak"},"stages":[{"name":"edit","role":"editor","model":"fake-model","prompt_version":"v-golden","prompt_sha256":"6a8e7f71139d6bc09a95159d1cb895e05b56d2f2f7417c5e20a7073c517c79f6","temperature":0.4,"reasoning":"off","capability":{"Budget":"max_tokens","Temp":"send","TempValue":0,"Reasoning":{"Control":"none","OffEffort":"","OffExtraBody":null,"OnExtraBody":null},"MinMaxTokens":4000}}]} brief_hash: c2021b1b5e6c20f6084e425b9a70bdc5f682801be347a57eb1c591deb8cab0df memory_version: 0a48d6ed9409de09f514af91aebdd622637621c9dc9464e8898c238791033d91 base_memory_version: bd451833d4ebb8a0ca07caf8deda76f027e1f42b78654987a22d215acf955eb7 @@ -178,58 +178,58 @@ chunk ch8/0 disposition=flagged flag="sanitizer_stripped" final_text="Судзу stage=edit role=editor model=fake-model resume=true disp=flagged flag="sanitizer_stripped" attempts=1 escalated=false esc_model="" finish="sanitized_export" cum_usd=0.00182 detail="CJK leak in the ru output: 特产" stage_text="" recovered="Судзуки нашёл древний камень на каменном алтаре." -- chunk_status -- -ch1/0 draft snap_match=true content_hash=3e156226b9a74aa70c8a259c62e3d2fe68486a96d2178d31084c1e38a3256241 disp=ok flag="" attempts=1 final_hash=70bca3400d2d8d848af27845cb2b86f362cdcf7a3a73e2611c46ec05feb7c4c2 cost=0.00182 escalated=false esc_model="" detail="" -ch1/0 edit snap_match=true content_hash=5e79dd5cbd310ac01d71ca258002beca18db715dfb8b1a08907508e506595355 disp=ok flag="" attempts=1 final_hash=799b6e6419910a2e52dd4652f2bde9bee59fbdb92a849f291ed33a5e30410a15 cost=0.00182 escalated=false esc_model="" detail="" -ch1/1 draft snap_match=true content_hash=82fb7f985de6c07182e8c09a3ceaad94f2e63745b87024087e437fba762b8227 disp=ok flag="" attempts=1 final_hash=ad47812002136f356e16d4fe4d71b73fdf1f2fbbe4f6a2cecd696abb07b7502d cost=0.00182 escalated=false esc_model="" detail="" -ch2/0 draft snap_match=true content_hash=cfab1e09264a3a30aba95e7da1d6a269468c6aa0c1b334d9241e78bc266a3571 disp=ok flag="" attempts=1 final_hash=35d5f692c908d3c15b40757a167c0deb3067e90492a4490500b0a0ccf359c9e2 cost=0.00728 escalated=true esc_model="fake-fallback" detail="" -ch2/0 edit snap_match=true content_hash=55ffd839a4a26c03cf57e08000ab2c84b267e1429ab4a8e212d3a01fda757632 disp=ok flag="" attempts=1 final_hash=e3f8acb903b0dd5798636597412acadfcf03a968ff049a8604b1142dea5cded1 cost=0.00182 escalated=false esc_model="" detail="" +ch1/0 draft snap_match=true content_hash=3e156226b9a74aa70c8a259c62e3d2fe68486a96d2178d31084c1e38a3256241 disp=ok flag="" attempts=1 final_hash=58e9dbc06bfa575bbb3e1e108d565a8a48a2e07dbcc1b14633adf1b20c33bf21 cost=0.00182 escalated=false esc_model="" detail="" +ch1/0 edit snap_match=true content_hash=5e79dd5cbd310ac01d71ca258002beca18db715dfb8b1a08907508e506595355 disp=ok flag="" attempts=1 final_hash=366482169be171810b4478c7a10245779c921afdd0daf3c443a60495bf2ef737 cost=0.00182 escalated=false esc_model="" detail="" +ch1/1 draft snap_match=true content_hash=82fb7f985de6c07182e8c09a3ceaad94f2e63745b87024087e437fba762b8227 disp=ok flag="" attempts=1 final_hash=f62d390f0624ddcfbc27b048475b749d06b44ab91ade175985b162160235f6e7 cost=0.00182 escalated=false esc_model="" detail="" +ch2/0 draft snap_match=true content_hash=cfab1e09264a3a30aba95e7da1d6a269468c6aa0c1b334d9241e78bc266a3571 disp=ok flag="" attempts=1 final_hash=afc9e9ff7d6f1e4832365eefe29133bc8b8f70277fea9d8379d41a490c47f35c cost=0.00728 escalated=true esc_model="fake-fallback" detail="" +ch2/0 edit snap_match=true content_hash=55ffd839a4a26c03cf57e08000ab2c84b267e1429ab4a8e212d3a01fda757632 disp=ok flag="" attempts=1 final_hash=a57b6f62ebecf7cbdb04d8fce7679b68dedb4cb95dcac2605942aa3fdc864e05 cost=0.00182 escalated=false esc_model="" detail="" ch3/0 draft snap_match=true content_hash=21df44eb104900664bdaaeb2f716f51dcddbc81a71d845f6ba0f91c5bc4b2cfb disp=flagged flag="hard_refusal" attempts=1 final_hash= cost=0.00728 escalated=true esc_model="" detail="provider finish_reason=refusal" ch3/0 edit snap_match=true content_hash= disp=skipped flag="hard_refusal" attempts=0 final_hash= cost=0 escalated=false esc_model="" detail="skipped: a member draft chunk of this edit unit was flagged (hard_refusal)" -ch4/0 draft snap_match=true content_hash=7aabdcb89dea365c95363b2662144ca1a10b307d377f47a8e7cc19b5b872d287 disp=ok flag="" attempts=1 final_hash=c74a4b18be42dae5946fc9d33f1172e6be71a37bad7dc08076e5fba59316e858 cost=0.00182 escalated=false esc_model="" detail="" -ch4/0 edit snap_match=true content_hash=2854dfe1648b0cf0cbb683cb7ce94c5eedc9223b2ce20c99dc008c7add6317ef disp=ok flag="" attempts=1 final_hash=5f8f5737a4e02766f8500915341c8b8a8827f8a05b03d77a746a4a8bc48806eb cost=0.00182 escalated=false esc_model="" detail="" -ch5/0 draft snap_match=true content_hash=2ed0a97f39cc5c57c62c3324f4646357372f26e3c5bae1773f24edf9449fc013 disp=ok flag="" attempts=1 final_hash=407f023c09e54b0399f79cf59e893817065b28129a807c57b25cc796202d75e1 cost=0.00182 escalated=false esc_model="" detail="" -ch5/0 edit snap_match=true content_hash=b145d78e4d30a39cc4088a1ddb8d934234c65341c62c79545c2eff1e7ae0c3d3 disp=ok flag="" attempts=1 final_hash=2b722376f3691a64c38d9a8026f567dfce0dd0a5ca3d2355bd2a95e4807ee67c cost=0.00182 escalated=false esc_model="" detail="" -ch6/0 draft snap_match=true content_hash=5f396d299a7ec5b10f3c8591e54ce7d84c4d31b815e561b0dd5f56635aef5a71 disp=ok flag="" attempts=1 final_hash=afd2169fce4c9aaecdf7f3de1f1f87bc08d5b672d19bc1dc772991f8d3321d60 cost=0.00182 escalated=false esc_model="" detail="" +ch4/0 draft snap_match=true content_hash=7aabdcb89dea365c95363b2662144ca1a10b307d377f47a8e7cc19b5b872d287 disp=ok flag="" attempts=1 final_hash=95953ad9afa465ccc21cf6ae02556fcc2259593e51fb7d2a50cd24741cedf2d3 cost=0.00182 escalated=false esc_model="" detail="" +ch4/0 edit snap_match=true content_hash=2854dfe1648b0cf0cbb683cb7ce94c5eedc9223b2ce20c99dc008c7add6317ef disp=ok flag="" attempts=1 final_hash=51a33a39df9ac10b3de8111d4e6c14c97e52d694d1853d0564a49f081017ffdf cost=0.00182 escalated=false esc_model="" detail="" +ch5/0 draft snap_match=true content_hash=2ed0a97f39cc5c57c62c3324f4646357372f26e3c5bae1773f24edf9449fc013 disp=ok flag="" attempts=1 final_hash=df9779e5705e186c4e936d4a1cbdb0bc66d603954bc7dd5ca2b7a4b06c2c9d0d cost=0.00182 escalated=false esc_model="" detail="" +ch5/0 edit snap_match=true content_hash=b145d78e4d30a39cc4088a1ddb8d934234c65341c62c79545c2eff1e7ae0c3d3 disp=ok flag="" attempts=1 final_hash=d331964b915e5496a8c377189df897bdd5c3535e09f9ff4382e75eee7073c681 cost=0.00182 escalated=false esc_model="" detail="" +ch6/0 draft snap_match=true content_hash=5f396d299a7ec5b10f3c8591e54ce7d84c4d31b815e561b0dd5f56635aef5a71 disp=ok flag="" attempts=1 final_hash=639d1fdb559d75d1ff72f73d72820e9aac3ff58f6b867408058f6f0761184155 cost=0.00182 escalated=false esc_model="" detail="" ch6/0 edit snap_match=true content_hash=592353321cc8000e792e0897446e4dd4f88a0280b9368b62cbd5c82b0c433193 disp=flagged flag="sanitizer_defect" attempts=1 final_hash= cost=0.00182 escalated=false esc_model="" detail="leading service preamble: Вот перевод фрагмента:" -ch7/0 draft snap_match=true content_hash=15c3ebbd56bb19880202d35b0206357655da245343376492f7ceb61808c8a5be disp=ok flag="" attempts=1 final_hash=486facff54a77c86f44cd322200caf4cf982975eeda552fa6dfdee4aa8df0904 cost=0.00182 escalated=false esc_model="" detail="" -ch7/0 edit snap_match=true content_hash=0b2674ed9829b2e6526ff41a60dd280b6670c539476526bbe835d9bf7a6be4c4 disp=flagged flag="sanitizer_stripped" attempts=1 final_hash=tm-sanitized-v1:def877b81bcdc52e2525fdcaae04b8c64f632c7d6747b31df7f4c7f1ad176e73 cost=0.00182 escalated=false esc_model="" detail="markdown header in the output: ### Глава 7" -ch8/0 draft snap_match=true content_hash=4a420977cc34ce738b4978e6a1baf45d78d773a135070c3b7f8c723adfa57d7b disp=ok flag="" attempts=1 final_hash=070d13df73e028346b69abe69253a999f109d92b01f39f3dfada0772be50e185 cost=0.00182 escalated=false esc_model="" detail="" -ch8/0 edit snap_match=true content_hash=c851100c8d6785f77d68f8127472790ae9d0408b7fec44785d3ff9d46842c6f8 disp=flagged flag="sanitizer_stripped" attempts=1 final_hash=tm-sanitized-v1:5095da45458381184b8798287c934e133c39668fed86c50cce921a1cb7df7893 cost=0.00182 escalated=false esc_model="" detail="CJK leak in the ru output: 特产" +ch7/0 draft snap_match=true content_hash=15c3ebbd56bb19880202d35b0206357655da245343376492f7ceb61808c8a5be disp=ok flag="" attempts=1 final_hash=64cd75a5c6410ff1d7c1880dd0ca06c871e517e9cef2ca7923a01fbcd88821ef cost=0.00182 escalated=false esc_model="" detail="" +ch7/0 edit snap_match=true content_hash=0b2674ed9829b2e6526ff41a60dd280b6670c539476526bbe835d9bf7a6be4c4 disp=flagged flag="sanitizer_stripped" attempts=1 final_hash=tm-sanitized-v1:93ae4ce8992136692f558f136d12483aff728b1a0aa54388072335b4a0231267 cost=0.00182 escalated=false esc_model="" detail="markdown header in the output: ### Глава 7" +ch8/0 draft snap_match=true content_hash=4a420977cc34ce738b4978e6a1baf45d78d773a135070c3b7f8c723adfa57d7b disp=ok flag="" attempts=1 final_hash=e9c1dd1174091275bf8f2975f1238d53b0b557ea1fbef283729a36dfa36f5f0d cost=0.00182 escalated=false esc_model="" detail="" +ch8/0 edit snap_match=true content_hash=c851100c8d6785f77d68f8127472790ae9d0408b7fec44785d3ff9d46842c6f8 disp=flagged flag="sanitizer_stripped" attempts=1 final_hash=tm-sanitized-v1:e6a81289fc023f94c28749e97eff499ec5222ed807b4a3c10934dbbcfe4a0ffc cost=0.00182 escalated=false esc_model="" detail="CJK leak in the ru output: 特产" -- request_log (insertion order) -- -ch1/0 draft role=translator req=fake-model actual=fake-model hash=70bca3400d2d8d848af27845cb2b86f362cdcf7a3a73e2611c46ec05feb7c4c2 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch1/1 draft role=translator req=fake-model actual=fake-model hash=ad47812002136f356e16d4fe4d71b73fdf1f2fbbe4f6a2cecd696abb07b7502d tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch2/0 draft role=translator req=fake-model actual=fake-model hash=56b4390260a43c1ffd2ba17afb8c3fab16113992856bb960e62e11c4937e0028 tm_hit=0 ok=0 finish="stop" degraded="cjk_artifact" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch2/0 draft role=translator req=fake-fallback actual=fake-fallback hash=35d5f692c908d3c15b40757a167c0deb3067e90492a4490500b0a0ccf359c9e2 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.0054600000000000004 tokens=1000/200/0/500/0 err="" -ch3/0 draft role=translator req=fake-model actual=fake-model hash=9e6325591eca8bc28e34be1da64106704d9589193c991f6545b22e8ddbae3b54 tm_hit=0 ok=0 finish="refusal" degraded="hard_refusal" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch3/0 draft role=translator req=fake-fallback actual=fake-fallback hash=544c921f47e59718019cfff42782f590deba18352123ae0687ceade0130fe2bd tm_hit=0 ok=0 finish="refusal" degraded="hard_refusal" cost=0.0054600000000000004 tokens=1000/200/0/500/0 err="" -ch4/0 draft role=translator req=fake-model actual=fake-model hash=c74a4b18be42dae5946fc9d33f1172e6be71a37bad7dc08076e5fba59316e858 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch5/0 draft role=translator req=fake-model actual=fake-model hash=407f023c09e54b0399f79cf59e893817065b28129a807c57b25cc796202d75e1 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch6/0 draft role=translator req=fake-model actual=fake-model hash=afd2169fce4c9aaecdf7f3de1f1f87bc08d5b672d19bc1dc772991f8d3321d60 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch7/0 draft role=translator req=fake-model actual=fake-model hash=486facff54a77c86f44cd322200caf4cf982975eeda552fa6dfdee4aa8df0904 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch8/0 draft role=translator req=fake-model actual=fake-model hash=070d13df73e028346b69abe69253a999f109d92b01f39f3dfada0772be50e185 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch1/0 edit role=editor req=fake-model actual=fake-model hash=799b6e6419910a2e52dd4652f2bde9bee59fbdb92a849f291ed33a5e30410a15 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch2/0 edit role=editor req=fake-model actual=fake-model hash=e3f8acb903b0dd5798636597412acadfcf03a968ff049a8604b1142dea5cded1 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch4/0 edit role=editor req=fake-model actual=fake-model hash=5f8f5737a4e02766f8500915341c8b8a8827f8a05b03d77a746a4a8bc48806eb tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch5/0 edit role=editor req=fake-model actual=fake-model hash=2b722376f3691a64c38d9a8026f567dfce0dd0a5ca3d2355bd2a95e4807ee67c tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch6/0 edit role=editor req=fake-model actual=fake-model hash=806e0871ca091d6ad598412d1ed78c711c0bc19412b1c20599d25ea1a7d3dc0f tm_hit=0 ok=0 finish="stop" degraded="sanitizer_defect" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch7/0 edit role=editor req=fake-model actual=fake-model hash=c445cf04d138dd1ecfae1b805336f2c5cde333b575b64f75c10f1017e39ac498 tm_hit=0 ok=0 finish="stop" degraded="sanitizer_stripped" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch8/0 edit role=editor req=fake-model actual=fake-model hash=1a3bc5b91428e2cc31a965748ea10046bf6d9f420f8c8d3e79f9fd3da5b03f4f tm_hit=0 ok=0 finish="stop" degraded="sanitizer_stripped" cost=0.00182 tokens=1000/200/0/500/0 err="" -ch1/0 draft role=translator req=fake-model actual=fake-model hash=70bca3400d2d8d848af27845cb2b86f362cdcf7a3a73e2611c46ec05feb7c4c2 tm_hit=1 ok=1 finish="stop" degraded="" cost=0 tokens=0/0/0/0/0 err="" -ch1/1 draft role=translator req=fake-model actual=fake-model hash=ad47812002136f356e16d4fe4d71b73fdf1f2fbbe4f6a2cecd696abb07b7502d tm_hit=1 ok=1 finish="stop" degraded="" cost=0 tokens=0/0/0/0/0 err="" -ch2/0 draft role=translator req=fake-model actual=fake-fallback hash=35d5f692c908d3c15b40757a167c0deb3067e90492a4490500b0a0ccf359c9e2 tm_hit=1 ok=1 finish="stop" degraded="" cost=0 tokens=0/0/0/0/0 err="" +ch1/0 draft role=translator req=fake-model actual=fake-model hash=58e9dbc06bfa575bbb3e1e108d565a8a48a2e07dbcc1b14633adf1b20c33bf21 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch1/1 draft role=translator req=fake-model actual=fake-model hash=f62d390f0624ddcfbc27b048475b749d06b44ab91ade175985b162160235f6e7 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch2/0 draft role=translator req=fake-model actual=fake-model hash=4c598abeab1852ea9cf980492f60a311382dd732e9c90e6146b3612bcc987cd2 tm_hit=0 ok=0 finish="stop" degraded="cjk_artifact" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch2/0 draft role=translator req=fake-fallback actual=fake-fallback hash=afc9e9ff7d6f1e4832365eefe29133bc8b8f70277fea9d8379d41a490c47f35c tm_hit=0 ok=1 finish="stop" degraded="" cost=0.0054600000000000004 tokens=1000/200/0/500/0 err="" +ch3/0 draft role=translator req=fake-model actual=fake-model hash=5806ff3fbd200810f0f01581eaf39a489ffb53fbd718fd696235a4bd4cdda367 tm_hit=0 ok=0 finish="refusal" degraded="hard_refusal" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch3/0 draft role=translator req=fake-fallback actual=fake-fallback hash=667725b23cd72e129132d47da0ff7db069ff4cece653b2518b7f9756600efaca tm_hit=0 ok=0 finish="refusal" degraded="hard_refusal" cost=0.0054600000000000004 tokens=1000/200/0/500/0 err="" +ch4/0 draft role=translator req=fake-model actual=fake-model hash=95953ad9afa465ccc21cf6ae02556fcc2259593e51fb7d2a50cd24741cedf2d3 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch5/0 draft role=translator req=fake-model actual=fake-model hash=df9779e5705e186c4e936d4a1cbdb0bc66d603954bc7dd5ca2b7a4b06c2c9d0d tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch6/0 draft role=translator req=fake-model actual=fake-model hash=639d1fdb559d75d1ff72f73d72820e9aac3ff58f6b867408058f6f0761184155 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch7/0 draft role=translator req=fake-model actual=fake-model hash=64cd75a5c6410ff1d7c1880dd0ca06c871e517e9cef2ca7923a01fbcd88821ef tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch8/0 draft role=translator req=fake-model actual=fake-model hash=e9c1dd1174091275bf8f2975f1238d53b0b557ea1fbef283729a36dfa36f5f0d tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch1/0 edit role=editor req=fake-model actual=fake-model hash=366482169be171810b4478c7a10245779c921afdd0daf3c443a60495bf2ef737 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch2/0 edit role=editor req=fake-model actual=fake-model hash=a57b6f62ebecf7cbdb04d8fce7679b68dedb4cb95dcac2605942aa3fdc864e05 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch4/0 edit role=editor req=fake-model actual=fake-model hash=51a33a39df9ac10b3de8111d4e6c14c97e52d694d1853d0564a49f081017ffdf tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch5/0 edit role=editor req=fake-model actual=fake-model hash=d331964b915e5496a8c377189df897bdd5c3535e09f9ff4382e75eee7073c681 tm_hit=0 ok=1 finish="stop" degraded="" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch6/0 edit role=editor req=fake-model actual=fake-model hash=1ec612b83d1bd0e657e40e189a988546406929290acbed8babcf230ac9ae592d tm_hit=0 ok=0 finish="stop" degraded="sanitizer_defect" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch7/0 edit role=editor req=fake-model actual=fake-model hash=4e4d6214cb7d85753935d8fc18a8a6fcf1f99bdef9ff4bc958a30749071af75b tm_hit=0 ok=0 finish="stop" degraded="sanitizer_stripped" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch8/0 edit role=editor req=fake-model actual=fake-model hash=fcc423a73253a774560ba8376f2c3c2db613eea3a74c3785772b56b3f36ce5ce tm_hit=0 ok=0 finish="stop" degraded="sanitizer_stripped" cost=0.00182 tokens=1000/200/0/500/0 err="" +ch1/0 draft role=translator req=fake-model actual=fake-model hash=58e9dbc06bfa575bbb3e1e108d565a8a48a2e07dbcc1b14633adf1b20c33bf21 tm_hit=1 ok=1 finish="stop" degraded="" cost=0 tokens=0/0/0/0/0 err="" +ch1/1 draft role=translator req=fake-model actual=fake-model hash=f62d390f0624ddcfbc27b048475b749d06b44ab91ade175985b162160235f6e7 tm_hit=1 ok=1 finish="stop" degraded="" cost=0 tokens=0/0/0/0/0 err="" +ch2/0 draft role=translator req=fake-model actual=fake-fallback hash=afc9e9ff7d6f1e4832365eefe29133bc8b8f70277fea9d8379d41a490c47f35c tm_hit=1 ok=1 finish="stop" degraded="" cost=0 tokens=0/0/0/0/0 err="" ch3/0 draft role=translator req=fake-model actual=fake-model hash= tm_hit=1 ok=0 finish="" degraded="hard_refusal" cost=0 tokens=0/0/0/0/0 err="" -ch4/0 draft role=translator req=fake-model actual=fake-model hash=c74a4b18be42dae5946fc9d33f1172e6be71a37bad7dc08076e5fba59316e858 tm_hit=1 ok=1 finish="stop" degraded="" cost=0 tokens=0/0/0/0/0 err="" -ch5/0 draft role=translator req=fake-model actual=fake-model hash=407f023c09e54b0399f79cf59e893817065b28129a807c57b25cc796202d75e1 tm_hit=1 ok=1 finish="stop" degraded="" cost=0 tokens=0/0/0/0/0 err="" -ch6/0 draft role=translator req=fake-model actual=fake-model hash=afd2169fce4c9aaecdf7f3de1f1f87bc08d5b672d19bc1dc772991f8d3321d60 tm_hit=1 ok=1 finish="stop" degraded="" cost=0 tokens=0/0/0/0/0 err="" -ch7/0 draft role=translator req=fake-model actual=fake-model hash=486facff54a77c86f44cd322200caf4cf982975eeda552fa6dfdee4aa8df0904 tm_hit=1 ok=1 finish="stop" degraded="" cost=0 tokens=0/0/0/0/0 err="" -ch8/0 draft role=translator req=fake-model actual=fake-model hash=070d13df73e028346b69abe69253a999f109d92b01f39f3dfada0772be50e185 tm_hit=1 ok=1 finish="stop" degraded="" cost=0 tokens=0/0/0/0/0 err="" -ch1/0 edit role=editor req=fake-model actual=fake-model hash=799b6e6419910a2e52dd4652f2bde9bee59fbdb92a849f291ed33a5e30410a15 tm_hit=1 ok=1 finish="stop" degraded="" cost=0 tokens=0/0/0/0/0 err="" -ch2/0 edit role=editor req=fake-model actual=fake-model hash=e3f8acb903b0dd5798636597412acadfcf03a968ff049a8604b1142dea5cded1 tm_hit=1 ok=1 finish="stop" degraded="" cost=0 tokens=0/0/0/0/0 err="" -ch4/0 edit role=editor req=fake-model actual=fake-model hash=5f8f5737a4e02766f8500915341c8b8a8827f8a05b03d77a746a4a8bc48806eb tm_hit=1 ok=1 finish="stop" degraded="" cost=0 tokens=0/0/0/0/0 err="" -ch5/0 edit role=editor req=fake-model actual=fake-model hash=2b722376f3691a64c38d9a8026f567dfce0dd0a5ca3d2355bd2a95e4807ee67c tm_hit=1 ok=1 finish="stop" degraded="" cost=0 tokens=0/0/0/0/0 err="" +ch4/0 draft role=translator req=fake-model actual=fake-model hash=95953ad9afa465ccc21cf6ae02556fcc2259593e51fb7d2a50cd24741cedf2d3 tm_hit=1 ok=1 finish="stop" degraded="" cost=0 tokens=0/0/0/0/0 err="" +ch5/0 draft role=translator req=fake-model actual=fake-model hash=df9779e5705e186c4e936d4a1cbdb0bc66d603954bc7dd5ca2b7a4b06c2c9d0d tm_hit=1 ok=1 finish="stop" degraded="" cost=0 tokens=0/0/0/0/0 err="" +ch6/0 draft role=translator req=fake-model actual=fake-model hash=639d1fdb559d75d1ff72f73d72820e9aac3ff58f6b867408058f6f0761184155 tm_hit=1 ok=1 finish="stop" degraded="" cost=0 tokens=0/0/0/0/0 err="" +ch7/0 draft role=translator req=fake-model actual=fake-model hash=64cd75a5c6410ff1d7c1880dd0ca06c871e517e9cef2ca7923a01fbcd88821ef tm_hit=1 ok=1 finish="stop" degraded="" cost=0 tokens=0/0/0/0/0 err="" +ch8/0 draft role=translator req=fake-model actual=fake-model hash=e9c1dd1174091275bf8f2975f1238d53b0b557ea1fbef283729a36dfa36f5f0d tm_hit=1 ok=1 finish="stop" degraded="" cost=0 tokens=0/0/0/0/0 err="" +ch1/0 edit role=editor req=fake-model actual=fake-model hash=366482169be171810b4478c7a10245779c921afdd0daf3c443a60495bf2ef737 tm_hit=1 ok=1 finish="stop" degraded="" cost=0 tokens=0/0/0/0/0 err="" +ch2/0 edit role=editor req=fake-model actual=fake-model hash=a57b6f62ebecf7cbdb04d8fce7679b68dedb4cb95dcac2605942aa3fdc864e05 tm_hit=1 ok=1 finish="stop" degraded="" cost=0 tokens=0/0/0/0/0 err="" +ch4/0 edit role=editor req=fake-model actual=fake-model hash=51a33a39df9ac10b3de8111d4e6c14c97e52d694d1853d0564a49f081017ffdf tm_hit=1 ok=1 finish="stop" degraded="" cost=0 tokens=0/0/0/0/0 err="" +ch5/0 edit role=editor req=fake-model actual=fake-model hash=d331964b915e5496a8c377189df897bdd5c3535e09f9ff4382e75eee7073c681 tm_hit=1 ok=1 finish="stop" degraded="" cost=0 tokens=0/0/0/0/0 err="" ch6/0 edit role=editor req=fake-model actual=fake-model hash= tm_hit=1 ok=0 finish="" degraded="sanitizer_defect" cost=0 tokens=0/0/0/0/0 err="" -ch7/0 edit role=editor req=fake-model actual=fake-model hash=tm-sanitized-v1:def877b81bcdc52e2525fdcaae04b8c64f632c7d6747b31df7f4c7f1ad176e73 tm_hit=1 ok=0 finish="sanitized_export" degraded="sanitizer_stripped" cost=0 tokens=0/0/0/0/0 err="" -ch8/0 edit role=editor req=fake-model actual=fake-model hash=tm-sanitized-v1:5095da45458381184b8798287c934e133c39668fed86c50cce921a1cb7df7893 tm_hit=1 ok=0 finish="sanitized_export" degraded="sanitizer_stripped" cost=0 tokens=0/0/0/0/0 err="" +ch7/0 edit role=editor req=fake-model actual=fake-model hash=tm-sanitized-v1:93ae4ce8992136692f558f136d12483aff728b1a0aa54388072335b4a0231267 tm_hit=1 ok=0 finish="sanitized_export" degraded="sanitizer_stripped" cost=0 tokens=0/0/0/0/0 err="" +ch8/0 edit role=editor req=fake-model actual=fake-model hash=tm-sanitized-v1:e6a81289fc023f94c28749e97eff499ec5222ed807b4a3c10934dbbcfe4a0ffc tm_hit=1 ok=0 finish="sanitized_export" degraded="sanitizer_stripped" cost=0 tokens=0/0/0/0/0 err="" -- retrieval_state -- ch1/0 snap_match=true exact=3 sticky=0 ambiguous=0 spoiler=1 evicted=0 postcheck_miss=1 style_flags=0 trust_gated=0 injected_ids=["紋章\u001f\u001f0\u001f0","鈴木\u001f\u001f0\u001f0","魔法学院\u001f\u001f0\u001f0"] postcheck_detail=[{"src":"紋章","dst":"герб","disp":"confirmed"}] style_detail= trust_gate_detail= diff --git a/backend/internal/terminology/series_test.go b/backend/internal/terminology/series_test.go index 09f4932e..1c0f6d2f 100644 --- a/backend/internal/terminology/series_test.go +++ b/backend/internal/terminology/series_test.go @@ -1,10 +1,88 @@ package terminology import ( + "os" + "path/filepath" + "sort" "strings" "testing" ) +// loadBankFullSurfaces reads the distilled BANK-FULL source surfaces (fix-pack §ж fixture) as candidates. +func loadBankFullSurfaces(t *testing.T) []Candidate { + t.Helper() + b, err := os.ReadFile(filepath.Join("testdata", "bankfull-surfaces.txt")) + if err != nil { + t.Fatal(err) + } + var cands []Candidate + for _, ln := range strings.Split(string(b), "\n") { + s := strings.TrimSpace(ln) + if s == "" || strings.HasPrefix(s, "#") { + continue + } + cands = append(cands, cand(s, 1)) + } + return cands +} + +// TestDetectSeriesOnBankFullFixtureIsStable pins the head-aware rule's LIVE property on the real corpus it was +// calibrated on (fix-pack §ж): exactly the measured grade/rank series form, none of them a family blob, and +// 方源/元石/元海 (the protagonist and two same-prefix-different-head entities) stay OUT of every series — the +// naive "differ in one position" rule produced an 11-surface blob here. +func TestDetectSeriesOnBankFullFixtureIsStable(t *testing.T) { + got := DetectSeries(loadBankFullSurfaces(t), zhSeries) + + // The FOUR grade/rank series the corpus forms, each sharing one real head — 转 (rank), 等 (grade), 等资质 + // (graded aptitude), 阶 (tier). Legitimately large is not a blob: 一转…九转 is nine ranks sharing 转, the + // canonical §1 case. A blob is a series that MIXES families under a shared rune, which the head-aware rule + // forbids by construction — pinned here by requiring each series be EXACTLY its expected group. + expected := [][]string{ + {"一转", "二转", "三转", "四转", "五转", "六转", "九转"}, + {"甲等", "乙等", "丙等", "丁等"}, + {"甲等资质", "乙等资质", "丙等资质"}, + {"初阶", "中阶", "高阶"}, + } + ids := map[int]bool{} + size := map[int]int{} + for _, id := range got { + ids[id] = true + size[id]++ + } + if len(ids) != 4 { + grouped := map[int][]string{} + for k, id := range got { + grouped[id] = append(grouped[id], k) + } + for id, ms := range grouped { + sort.Strings(ms) + t.Logf("series %d: %v", id, ms) + } + t.Fatalf("expected exactly 4 series on the distilled bank, got %d", len(ids)) + } + for _, grp := range expected { + id := got[grp[0]] + if id == 0 { + t.Fatalf("expected group %v did not form a series", grp) + } + for _, k := range grp { + if got[k] != id { + t.Fatalf("%s must join series %d with %s, got %d", k, id, grp[0], got[k]) + } + } + if size[id] != len(grp) { + t.Fatalf("series %d has %d members, expected exactly %d (%v) — a member leaked in (a blob)", id, size[id], len(grp), grp) + } + } + // The three entities that must NEVER be series members: the protagonist 方源 and the two same-prefix, + // different-HEAD entities 元石/元海 (石/海 differ IN the head → different entities, the §2 type step's job). + for _, k := range []string{"方源", "元石", "元海"} { + if got[k] != 0 { + t.Fatalf("%s must stay OUT of every series, got id %d", k, got[k]) + } + } +} + var zhSeries = SeriesParams{Enabled: true, HeadFinal: true} func cand(key string, freq int) Candidate { return Candidate{Key: key, Src: key, Type: "term", Freq: freq} } @@ -101,6 +179,20 @@ func TestBatchKeepsSeriesWhole(t *testing.T) { } } +// TestBatchRunesFlagsOversize pins the fix-pack §в observability seam: BatchRunes measures a batch the way +// Batch packs it, so the terminologist can WARN when §1 keeps a unit whole past the budget instead of +// silently over-running the output cap. A lone candidate too big for the budget stays ONE over-cap batch. +func TestBatchRunesFlagsOversize(t *testing.T) { + big := Candidate{Key: "元", Src: "元", Type: "term", KWIC: []string{strings.Repeat("к", 500)}} + if BatchRunes([]Candidate{big}) <= 100 { + t.Fatal("a candidate carrying a 500-rune context must render well over a 100-rune budget") + } + batches := Batch([]Candidate{big}, 100, nil) + if len(batches) != 1 || BatchRunes(batches[0]) <= 100 { + t.Fatalf("an oversize lone candidate must stay one over-cap batch the caller can WARN on: %v", batches) + } +} + // TestBatchValueOrder: batches are processed most-frequent first, so a budget ceiling drops the rarest terms // (feed_cap) rather than the lexicographic tail. Content of each batch is untouched by the ordering. func TestBatchValueOrder(t *testing.T) { diff --git a/backend/internal/terminology/terminology.go b/backend/internal/terminology/terminology.go index 58aadf8e..1b7fbd8c 100644 --- a/backend/internal/terminology/terminology.go +++ b/backend/internal/terminology/terminology.go @@ -917,10 +917,17 @@ func ParseReply(reply string, expected []string, normalize func(string) string, // Batch splits candidates into groups whose rendered size stays under maxRunes. A series (a key present in // seriesID; pass nil to disable the channel) is kept WHOLE in one batch so the terminologist picks one // generic head for it (§1); a series or a single candidate larger than the budget still gets its own batch -// rather than being split or silently dropped. The batches are then ordered by descending source frequency, -// so a budget ceiling drops the least-frequent terms instead of the lexicographic tail (feed_cap, §7). Every -// batch's CONTENT — hence its request hash and $0 resume — is independent of this ordering; only the call -// order moves. Deterministic throughout. +// rather than being split or silently dropped (the caller WARNs on such an over-cap unit — see BatchRunes — +// because the whole unit still bills against one output cap). The batches are then ordered by descending +// source frequency, so a budget ceiling drops the least-frequent terms instead of the lexicographic tail +// (feed_cap, §7). +// +// The ordering is DETERMINISTIC — a stable sort on a pure function of each batch's content — so the same +// input reproduces the same batch order every run and the $0 checkpoint resume holds. It is NOT, however, +// independent of that ordering: the consumer addresses batch i as ChunkIdx i (terminologist.go), which folds +// into the request hash, so a batch's POSITION is part of its address. If a later run's frequencies reorder +// the batches their ordinals shift and the affected batches re-pay — the accepted price of value-ordering, +// and no worse than the content edit that would already re-pay them. func Batch(cands []Candidate, maxRunes int, seriesID map[string]int) [][]Candidate { if len(cands) == 0 { return nil @@ -959,6 +966,12 @@ func Batch(cands []Candidate, maxRunes int, seriesID map[string]int) [][]Candida return out } +// BatchRunes reports the rendered size RenderBatch produces for a whole batch — the same measure Batch packs +// against. The terminologist uses it to WARN when a co-batched series or a lone candidate exceeds the budget: +// §1 keeps such a unit WHOLE rather than split it, so it is a real over-cap call the operator must see coming +// (the cap-8000 output mine, PROBES §1), not a silent overrun. +func BatchRunes(batch []Candidate) int { return unitRunes(batch) } + // unitRunes is the rendered size the accountant charges a unit: each member's single-block render plus the // blank line RenderBatch puts between blocks (+2). Without the +2 a 200-term book overruns its budget by // 2×(N−1) runes. Summing per member matches the original per-candidate arithmetic exactly. diff --git a/backend/internal/terminology/testdata/bankfull-surfaces.txt b/backend/internal/terminology/testdata/bankfull-surfaces.txt new file mode 100644 index 00000000..96ce9172 --- /dev/null +++ b/backend/internal/terminology/testdata/bankfull-surfaces.txt @@ -0,0 +1,155 @@ +# BANK-FULL surface distillation (fix-pack §ж, D39.71): the SOURCE surfaces of the gu-zhenren coldrun-a +# bank (books/gu-zhenren/coldrun-a/BANK-FULL.tsv, col 1), the corpus DetectSeries was calibrated on. Pinned +# so the head-aware series rule's live property — exactly the measured grade/rank series form, no family +# blob, and 方源/元石/元海 stay OUT — cannot silently regress. Pair TEST DATA (legit per the generality +# mandate); one source surface per line, comments with '#'. +方源 +古月 +方正 +转 +家老 +蛊师 +窍 +舅父 +点 +舅母 +真元 +月光蛊 +蛊虫 +元石 +族长 +舅父舅母 +空窍 +炼化 +元海 +甲等 +丙等 +山寨 +沈翠 +开窍 +学堂家老 +酒虫 +一转 +困境 +甲等资 +甲等资质 +古月方正 +花酒行者 +人祖 +开窍大典 +古月一族 +春秋蝉 +丙等资 +丙等资质 +乙等 +古月赤练 +花海 +蛊室 +古月山寨 +本命蛊 +三转 +乙等资质 +古月族长 +古月赤城 +四代族长 +希望蛊 +光点 +古月方源 +古月漠北 +青茅山 +二转 +五转 +古月漠尘 +炼蛊 +力量蛊 +智慧蛊 +沈嬷嬷 +溶洞 +血脉 +一转蛊师 +丁等 +修行资质 +光膜 +四转 +方家 +灵泉 +高脚吊楼 +魔道 +三转蛊师 +九转 +光团 +六转 +初阶 +地下溶洞 +家主阁 +方之一脉 +时辰 +白凝冰 +空窍元海 +精气神 +贴身丫鬟 +一转蛊虫 +三蛊 +元气 +凡人 +古月师 +古月陈博 +巅峰 +月兰花 +物是人非 +白家寨 +真元海 +管家 +过继 +邦邦 +酒气 +酒肆 +青铜真元 +魔道巨擘 +一转巅峰 +中土 +中阶 +九转境界 +五更天 +侧室 +元海空窍 +光阴之河 +十大奇蛊 +华池 +南疆 +古月冻土 +古月博 +古月藻榭 +咏梅 +天有不测风云 +宗祖祠堂 +将敬酒 +彩雀儿 +抽经伐髓 +拂开 +方家支脉 +更夫 +梆子 +江城子 +池塘 +熊家寨 +珍珠簪子 +真元海洋 +矛竹 +空窍外侧 +竹楼 +紫府 +绣花鞋 +老妈子 +花酒行者遗藏 +赤之一脉 +赤脉 +趺坐 +逆光阴五百年觉悟 +采花大盗 +难兄难弟 +雨帘 +雪盐 +青铜元海 +青铜海 +高阶 diff --git a/backend/internal/text/runes.go b/backend/internal/text/runes.go index 7e63f7ec..21edaa5d 100644 --- a/backend/internal/text/runes.go +++ b/backend/internal/text/runes.go @@ -33,6 +33,21 @@ func RunesEqual(a, b []rune) bool { // those checks need. The word SCRIPT is the target's declared alphabet (data), so the tokenizer is target- // general; the ё/е distinction the yofication check measures is preserved (ё is a Cyrillic letter). func TokenizeScript(text string, ws *unicode.RangeTable) []string { + return tokenizeWords(text, func(r rune) bool { return unicode.Is(ws, r) }) +} + +// TokenizeLetters is the SCRIPT-AGNOSTIC sibling of TokenizeScript: maximal runs of any Unicode letter, +// lower-cased, with the same combining-mark handling. For a caller that matches words regardless of the +// target's alphabet and has no declared script to pass — the decl-aware bank post-check tokenizes a target +// term head against the output without needing (or knowing) the target's word script. lang.TokenizeWords +// wraps it, so `lang` (which MAY import `text`) keeps one tokenizer instead of a second copy of this loop. +func TokenizeLetters(text string) []string { + return tokenizeWords(text, unicode.IsLetter) +} + +// tokenizeWords is the shared core of TokenizeScript/TokenizeLetters: lower-case, then emit maximal runs of +// runes the predicate accepts, dropping a combining mark that sits on an open run rather than breaking it. +func tokenizeWords(text string, isWord func(rune) bool) []string { var words []string var b strings.Builder flush := func() { @@ -43,10 +58,10 @@ func TokenizeScript(text string, ws *unicode.RangeTable) []string { } for _, r := range strings.ToLower(text) { switch { - case unicode.Is(ws, r): + case isWord(r): b.WriteRune(r) case b.Len() > 0 && unicode.Is(unicode.Mn, r): - // a combining mark sits ON the preceding word-script base: keep the run open and drop the mark. + // a combining mark sits ON the preceding word base: keep the run open and drop the mark. default: flush() } diff --git a/backend/internal/text/tokenize_test.go b/backend/internal/text/tokenize_test.go new file mode 100644 index 00000000..a8488aec --- /dev/null +++ b/backend/internal/text/tokenize_test.go @@ -0,0 +1,31 @@ +package text + +import ( + "strings" + "testing" + "unicode" +) + +// TestTokenizeLetters pins the script-agnostic tokenizer the bank post-check uses (fix-pack §и consolidation): +// maximal any-letter runs, lower-cased, with a combining mark dropped rather than splitting the word — the +// same combining-mark handling TokenizeScript has, now shared through one core. +func TestTokenizeLetters(t *testing.T) { + got := TokenizeLetters("Фан Юаню, к Горе!") + if strings.Join(got, "|") != "фан|юаню|к|горе" { + t.Fatalf("tokens = %v", got) + } + // A stress accent (о + U+0301) must not split the word — the pack-21 #11 fix, now inherited by the + // decl post-check that lang.TokenizeWords feeds. + if got := TokenizeLetters("сло́во"); len(got) != 1 || got[0] != "слово" { + t.Fatalf("a combining mark must not split the word, got %v", got) + } + // Script-agnostic: a Latin word and a Cyrillic word both tokenize (TokenizeScript would drop one). + if got := TokenizeLetters("Гу and Gu"); strings.Join(got, "|") != "гу|and|gu" { + t.Fatalf("any-letter tokenization must keep both scripts, got %v", got) + } + // It agrees with TokenizeScript on a single-script string (the shared core), so the consolidation is + // behaviour-preserving for the target lexical checkers. + if a, b := TokenizeLetters("Меч и Щит"), TokenizeScript("Меч и Щит", unicode.Cyrillic); strings.Join(a, "|") != strings.Join(b, "|") { + t.Fatalf("TokenizeLetters and TokenizeScript diverged on Cyrillic-only text: %v vs %v", a, b) + } +} diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index c5500ae9..640fa8c4 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -1,8 +1,8 @@ # Журнал прогресса -> **⟶ ТЕКУЩЕЕ СОСТОЯНИЕ** (на 2026-08-02, голова D39.74). **Источник истины по РЕШЕНИЯМ — `architecture/05-decisions-log.md` (D1–D39.74); этот файл — ЖУРНАЛ.** +> **⟶ ТЕКУЩЕЕ СОСТОЯНИЕ** (на 2026-08-02, голова D39.75). **Источник истины по РЕШЕНИЯМ — `architecture/05-decisions-log.md` (D1–D39.75); этот файл — ЖУРНАЛ.** > - **Сделано (сводно; детали — D-лог и архив-слайсы):** Ф0 ✅ · Ф1-инфра ✅ · арх-ресет D39 (7 слоёв, паки 11–16) ✅ · паки 17 «канал B» · 18 «долги» · 19 «голос/состояние» · 20 «банк+терминолог» ✅ (D39.26–28/31/41–56) · мини-прогон (D39.37) и ХОЛОДНЫЙ прогон (D39.58: recall банка 0.918/0.980, банк приходит переведённым) приняты · полигон-пакеты 5–8, ToS-речек (D39.57), Р6+P4 (D39.61) закрыты · **карта языковой привязки движка (D39.60): 149 сайтов/45 файлов, книго-ось чиста (4), ja→ru безопасно без правки Go, en→ru — нет** · Р1–Р4 закрыты целиком (D39.63: Р2 = ЗНАЧЕНИЕ, Р4 = норма потолка recall) · **ФАЗА 2 ОБЩНОСТИ ✅ (D39.64: П0 эмбед-хеш · П1 цель-шов · П2 скрипт-шов, ko-баг закрыт · П3 нарезка · П4 манифест-по-каналам; голден вердикт-нейтрален 0/172, майнер-парити EXACT)** · жанровый словарь отменён как класс (D39.47) · петля ремонта построена и НЕ включена (D39.38). -> - **Курс (D39.59–69): ОБЩНОСТЬ ✅ → КАЧЕСТВО БАНКА ✅ (ядро построено).** Банк-пак принят целиком (D39.69: серия-батчинг head-aware · тип-классификатор первичным · Decl-стеммер+neuter; 36а/36в/27/84/feed_cap закрыты; голден вердикт-нейтрален, «4 серии на живом BANK-FULL» воспроизведено независимо). **Текущее: ФИКС-ПАК БАНКА ПЕРВЫМ (промт `BACKEND_BANK_FIXPACK_SESSION_PROMPT.md` выдан D39.71, строка 87) → после его лендинга ПАКЕТ-ЧЕКЕРОВ (промт выдан, общие файлы membank/prompts — не параллелить). Вопросы §9 закрыты (D39.70).** ⚠ **DeepSeek-V4-Flash-0731: платные прогоны СТОП до ре-пробы (строка 74; при ре-пробе добрать платный порог §2 «6/6 harm»). Перекупок нет — стендовый корпус одноразовый (D39.63).** +> - **Курс (D39.59–75): ОБЩНОСТЬ ✅ → КАЧЕСТВО БАНКА ✅ (ядро D39.69 + фикс-пак D39.75).** **Текущее: очередь за ПАКЕТОМ-ЧЕКЕРОВ (промт `BACKEND_CHECKER_PACKAGE_SESSION_PROMPT.md` выдан, теперь можно запускать — фикс-пак заленден) + строка 91 у владельца (Decl мягкознаковых имён: forms[] vs anchor-gated).** ⚠ **DeepSeek-V4-Flash-0731: платные прогоны СТОП до ре-пробы (строка 74; там же 6/6-порог классификатора и слоты Q2/Q7). Перекупок нет (D39.63).** > - **Горизонт (D39.62/67):** пакет-чекеров (строка 25) + фикс-лист банка (87) → ре-проба flash (74; + платный порог 6/6 классификатора + пробы 36б-замера Q2/эмиссии Q7 по слову) → **ДОБОР ИДЕАЛА** (первым прогоном: оси голоса 24 · авто-режим · итерация №2 редакторов 65 · цена 16 · веса K1–K12 13а · вне-претрейн чекпоинт 55) → ВТОРАЯ ПАРА живьём (ja→ru; преп 81) → МАСШТАБ → пилот Ф2.5 (гейт резюме-строки 80; строки 62–68, 85) → Ф3 ридер-IDE (69–71). **Стоячие:** ToS-триггер 25.10 · Ш-2 до go1.27 · строка 74 перед любым платным прогоном. > - **Стек:** draft deepseek-v4-flash thinking-ON (+банкнота) **⚠0731** → терминолог (та же модель, батчи, экран `target_script`) → editor deepseek-v4-pro БИЛИНГВ ИНТЕРИМ (вендором НЕ тронут; glm-5 резерв) → судья gemini (Ф2, полигон); канал B Mistral+grok; ~$0.85/ранобэ (D30.4) — пере-калибровка после развилки 74. > - **ЕДИНЫЙ БЭКЛОГ — секция «Бэклог» ниже** (одна таблица, единственный трекер; каждая петля обязана иметь диспозицию: решено / отложено-с-записью / отклонено; ведёт оркестратор). @@ -30,6 +30,7 @@ | 13б | `speech-cue.txt` zh НЕ шипить до замера голос-флаггера на холодном прогоне (данные для недоказанного детектора = инерция; вывод обсуждения 31.07): оси пустые → файл не нужен, оси ловят → решение владельца о шипе (байты двигают `LangpackVersion`) | владелец | отложено-до-замера | первый прогон добора идеала (оси голоса, строка 24) → отдельное решение | D39.56, PACK19_BUILD §7.2, чат 31.07 | | 14 | Четыре data-инженерных флага фазы-2 общности, каждый с рекомендованным дизайном (§5 отчёта): ё-фолд → target-данные (`char_fold`; смена сигнатуры core-примитива через вердикт-несущий банк, сдвиг `memory_version`) · segmentation-фолбэк громким (ломает контракт corridor-фолбэка `pipeline.go:839-848` + голден-фикстура на нём; лечение — объявить блок в голден-yaml) · `Fertility` per-script map (калибровка ждёт боевого токенизатора) · `source-encoding.txt` + Go-реестр декодеров | владелец | когда-нибудь (следующий пак общности) | отдельное решение | D39.64, GENERALITY_PHASE2 §5.1–5.4 | | 86 | Мелкие открытые решения одним списком (свип D39.66): echo-семантика дропнутого c-lite-члена (D39.18 — оставить pre-c-lite?) · magnitude-кап тяжёлой CJK-утечки (D38.3 п.4в; сейчас strip+flag) | владелец | когда-нибудь | отдельные решения | D39.66, D39.69(feed_cap закрыт §1) | +| 91 | Decl-приёмка МЯГКОЗНАКОВЫХ имён (finding-1 фикс-пака, D39.75): стеммером НЕВОЗМОЖНА — «ь» неотличим от коллизии «Синь»↔«синий» (доказано контрфактикой, регресс-гвозди стоят); выбор: (а) сид/терминолог перечисляет `decl.forms[]` для -нь/-ль имён (ложится на Decl-эмиссию) · (б) anchor-gated матч; при решении добавить прямой тест-гвоздь на «Фан Юаню» и заменить инертную пару Линь/линию→Линь/линий | владелец → бэкенд | скоро (малый дизайн) | отдельное решение | D39.75, BANK_FIXPACK §а | | **— ХВОСТЫ ХОЛОДНОГО ПРОГОНА (исполнен достаточно, D39.58; Р7 владельца) —** | | | | | | | 16 | Полная цена холодного старта: edit-волна не гонялась (Р7); измерено всё ДО редактуры (драфт+банкнота $0.0247/волна · терминолог $0.006–0.013/проход) | бэкенд | когда-нибудь (первый прогон добора идеала) | добор идеала | D39.58 | | 18 | Эмиссия головы 蛊: сид-половина = чек-лист (определяющий терм книги сидится `allow_short`, D39.50); эмиссия-КАНАЛ демотирован до ИЗМЕР-ГЕЙТА (Q7): полигон-замер «меняет ли эмиссия головы рендер композитов» — строить ТОЛЬКО по эффекту и ПОСЛЕ #10 (однознаковый пост-чек чинится чекер-паком); перед стройкой резолвить K-гейт и KWIC-грязь одиночной головы | полигон → бэкенд | скоро (санкция Q7 D39.70 — после #10, очередью с ре-пробой) | полигон-замер → решение | D39.58, D39.60, D39.69 | @@ -37,8 +38,7 @@ | **— ТЕКУЩАЯ ОЧЕРЕДЬ (D39.59) —** | | | | | | | 74 | ⚠ **DeepSeek-V4-Flash-0731: боевая черновая форма покупает пустые ответы — блокер ПЛАТНЫХ прогонов.** Оперативное состояние = «(в) ждать» (D39.63; фаза-2 всё равно $0-код). Перед следующим платным прогоном — ре-проба flash за копейки → если не устаканилось, выбор: (а) флор 16000 (сдвиг request_hash безболезнен — перекупок на этой фазе нет; хвост не закрывает, упор 2/5) или (б) слать `reasoning_effort` явно (вендорская модель, дешевле ~22%; но амендмент `echoMineViolation`, эхо ~6% и качество черновика при `low` не судилось — нужен замер ~$0.05; тогда же предмет для постинструкции/префилла); включение ручки #77 (ре-ген эха перед эскалацией, построена D39.64, дефолт 0) — тем же решением; при включении помнить общий счётчик attempt с length-ре-геном + рост `maxTokensForAttempt`; остаток §12.7 (8 несудимых fidelity-пар ≈$0.10 · ja-вход ≈$0.005 · prefill-continuation research/21 §1.17) — вернуться при предмете wiring; ⚠ cap 8000 бьёт и СТАДИЮ ТЕРМИНОЛОГА (reasoning 8063/батч, D39.65) — резолюция обязана накрыть терминолога; тем же заходом: платный порог приёмки §2 классификатора (6/6 harm-набор, ~$0.01, D39.69) + полигон-слоты Q2 (36б-замер) и Q7 (эмиссия головы, после #10) | владелец (по ре-пробе) | перед следующим платным прогоном | ре-проба → решение | D39.61, D39.63, D39.64, POLYGON_PROMPTLANG_ANTIECHO §12.4 | | 78 | Леджер денег = НИЖНЯЯ граница: «2xx body decode failed (call IS billed)» — провайдер списал, исходная попытка в `request_log` НЕ попадает (≤$0.009/сессия); дизайн фикса ГОТОВ (GENERALITY_PHASE2 §5.6: `BilledDecodeFails` + per-billed-attempt settle с `Estimated=true`), но это money/ledger-путь — не $0-хастл | бэкенд | когда-нибудь (money-паком) | отдельное решение (дизайн §5.6) | D39.61, D39.64 | -| 79 | Остаточные Cyrillic-хардкоды пар-гейтед DC-чекеров: `checks/repair.go:131` (трим DC1-спана) · `checks/checkers.go:457,474` (`isCyrLetter` словограница register) — инертны без dc-данных пары, но пара С DC-чекерами и не-ru целью потребует правки Go; кандидат на `c.wordScript` ; + lintNumberMagnitude гейтить по объявленному письму источника (находка §8 карты D39.60) | бэкенд | когда-нибудь (следующий пак цель-оси) | ближайшее касание чекеров | D39.64 | -| 87 | Фикс-лист банк-пака (D39.69, приёмка): **«ь» (и класс -нь/-ль) в `decl_suffix` — приёмочный кейс «Фан Юаню»→«Фан Юань» сейчас ПРОВАЛИВАЕТСЯ (одна строка данных + пере-захват голдена)** · комментарий `Batch` «request hash independent of ordering» неверен (ChunkIdx фолдится — поправить) · оверсайз-серия одним батчем (помнить мину cap-8000 терминолога) · classify_types при выключенном terminology-гейте = тихий no-op + config-тесты classify-веток · мутационный тест neuter-фолда · voice-checker без стеммера (решить: заводить или объявить не-нужным) · дистилл-фикстура BANK-FULL (пин «4 серии») · `series`/`head_final` → строкой в `lang-script.txt` (сейчас Go-константа CJK-класса — D39.70) · консолидация `lang.TokenizeWords`↔`text.TokenizeScript` · промпт-инструкция роли «реалии — смыслом, транслит — именам» (Q5, данные пар-промпта) | бэкенд | в работе (промт выдан D39.71; ПЕРВЫМ, чекеры после) | `BACKEND_BANK_FIXPACK_SESSION_PROMPT.md` | D39.69 | +| 79 | Остаточные Cyrillic-хардкоды пар-гейтед DC-чекеров: `checks/repair.go:131` (трим DC1-спана) · `checks/checkers.go:457,474` (`isCyrLetter` словограница register) — инертны без dc-данных пары, но пара С DC-чекерами и не-ru целью потребует правки Go; кандидат на `c.wordScript` ; + lintNumberMagnitude гейтить по объявленному письму источника (находка §8 карты D39.60) ; + граница ПЛОТНОСТИ письма для серий: `enabled` через Go-константу `cjkScriptNames` (head-finality уже в данных script-series.txt; новый плотный скрипт требует правки Go — пре-существующая граница, D39.75) | бэкенд | когда-нибудь (следующий пак цель-оси) | ближайшее касание чекеров | D39.64 | | 89 | Остатки карты общности D39.60 без носителя (пропуск свипа, D39.72): ведро D «удалить» — живые: указатели на несуществующий `checkers_zh_ru.go` (cheapgates.go:56,125,130 · langpack.go:73+) · ложная шапка checkers.go:22-25 · дубль isHeaderContentRune↔isHeaderSeparator · minerPackVersion «zh-universal-v1» · глифы ⟨проверить⟩ tmctl/render · вес palladius miner.go (дюжину сверить по карте §3 — часть могла уйти фазой-2) + сверка ведра B (6 «пар-модуль без гейта») против фазы-2 | бэкенд | скоро | пакет-чекеров (промт дополнен) / мелкая пачка | D39.60, D39.66, D39.72 | | 90 | Ф2-кандидаты 02-mvp-плана без носителя (свип-хвост, D39.73): CometKiwi/T-index метрики судьи · Batch API судьи (экономия) · llama-server-интеграция локали · стриминг — взять/закрыть при пилот-препе | полигон/бэкенд | когда-нибудь (Ф2.5) | пилот-преп-промт (с 85) | 02-mvp-plan, D39.66, D39.73 | | **— НАХОДКИ СВИПА ПОЛНОТЫ (D39.66: 951 обязательство проверено, потери возвращены в трекер) —** | | | | | | @@ -104,6 +104,10 @@ | 69 | Gemini API «под-18» обязательство на конечный продукт (независимо от лейблов) | владелец | когда-нибудь | Ф3 / отдельное решение | D39.32, D39.34(7) | | 70 | Action-security gate перед выдачей tools/webfetch (D25 п.5) | бэкенд | когда-нибудь | Ф3 | D39.34(7) | | 71 | Планы research/22: epub-tag-rewrite · Q7-леджер ; + F3-brief из D29.3 (chat-edit · Not-useful-петля · cost-of-fix) + Ф3-скоуп 02-mvp-plan (TMX/TBX · Bertalign · дистилляция 7–14B) | бэкенд | когда-нибудь | Ф3 | D39.34(7) | +## Оркестратор №9 + бэкенд — ФИКС-ПАК БАНКА ПРИНЯТ И ЗАЛЕНДЕН (D39.75), 02.08 + +Отчёт: [`archive/reports/BANK_FIXPACK_2026-08-02.md`](archive/reports/BANK_FIXPACK_2026-08-02.md) (ревью-шапка). 9/9 пунктов отработаны: 6 сделано (head-finality в данных · правдивый Batch-коммент+WARN · classify fail-loud+тесты · мутационные neuter-тесты · фикстура 4-серий · TokenizeLetters-консолидация, побочно закрывшая combmark-щель), 2 «не делать» с контрактом (voice×стеммер; Q5 уже в промптах с D39.45), 1 ОТКАЧЕН правильно — «ь» ловил бы коллизию Синь/синий (мандат самопроверки сработал: адверсариальный ревьюер сессии поймал до лендинга). Верификация: батарея зелёная, голден маскированно ПУСТ, 14/15 CONFIRMED. Строка 87 закрыта; finding-1 → строка 91 (владелец); плотность-граница → дописка 79. Очередь: ПАКЕТ-ЧЕКЕРОВ можно запускать. + ## Оркестратор №9 — АУДИТ tmp-РЕСЁРЧЕЙ 21/22 (D39.74): утерянного нет, два хвоста моего триажа добиты, 02.08 По вопросу владельца о ресёрчах соседних харнессов (/home/ubuntu/projects/tmp, 15 клонов): research/21 транспорт — 9 пунктов построено паками 12/13, вердикт «транспорт не трогаем» ратифицирован, GLM-overflow/prefill-continuation уже несены (59/74), Q7-квирки — только после вендор-сверки (правило стоит); research/22 домен — Q6 gating+repair построен паком-16, Problem-классы в 25, planning в 71. Хвосты пучков моего триажа добиты: Mistral-strip гейт → дописка 59 (латентен при single-shot wire; жив для канала B/multi-turn); halve-chunk-on-retry №7 — ОТКЛОНЁН явно (волны+чекпойнты+флор делают малоценным; реопен при упорах — строка 45). @@ -176,6 +180,8 @@ Q1 = П1 цель-шов санкционирован («главное бэке ## Бэкенд +**Фикс-пак банка (строка 87 + Q5) исполнен (02.08, бэкенд-сессия по `BACKEND_BANK_FIXPACK_SESSION_PROMPT`, D39.71, $0 — только фейк-провайдер в тестах, НЕ закоммичено).** Отчёт: [archive/reports/BANK_FIXPACK_2026-08-02.md](archive/reports/BANK_FIXPACK_2026-08-02.md). СДЕЛАНО: **(б)** head_final → данные (`script-series.txt`+`SeriesHeadFinal`, zh/ja/ko байт-идентичны, тест «плотный не-head-final заводится данными») · **(в)** лживый коммент `Batch` переписан (ChunkIdx фолдится в request-hash) + `BatchRunes`-WARN на оверсайз-серию (не резать — убьёт со-батч §1) · **(г)** classify_types при выкл.гейте = fail-loud + 5 config-тестов · **(д)** мутационный neuter-фолд (mined-scope) · **(ж)** дистилл-фикстура `BANK-FULL` пинит ровно 4 серии (转/等/等资质/阶) · **(и)** `lang.TokenizeWords`→`text.TokenizeLetters` (общий шов, чинит combmark-щель). НЕ ДЕЛАТЬ с обоснованием: **(е)** стеммер НЕ в voice (accept-bias не единонаправлен; контракт в код) · **(з)** Q5-реалии УЖЕ в `terminologist.md`/`classifier.md` (пинг: `PromptSHA256` не двинулся). **(а) ОТКАЧЕНО — предложенный «ь» ФЛАВЕД: адверсариальное ревью (author≠reviewer, подтверждено white-box) поймало коллизию «Синь/Линь»→«син/лин»=«синий/линия» → ложный `SpoilerLeaks` (шум); «юань»→«юан» неотличим по форме — min-stem не разделяет.** Откат + регресс-гвоздь; finding 1 (палладий) открыт в safe-направлении, нужен сид-decl-перечень или anchor-gated матч — **РЕШЕНИЕ владельца (пинг §а)**. Приёмка: `build`/`vet`/`test -race ./...` зелёные · майнер-парити EXACT · голден пере-захвачен, маскированный дифф ПУСТ (двинулся только `embedded_version`; `memory_version`/`prompt_sha256`/`content_hash`/вердикты — на месте) · греп-общность чист. **СТОП — приёмка оркестратора.** + **Качество банка — этап 1 (дизайн) исполнен (01.08, бэкенд-сессия по `BACKEND_BANK_QUALITY_SESSION_PROMPT`, $0, read-only, НЕ закоммичено).** Отчёт: [archive/reports/BANK_QUALITY_DESIGN_2026-08-01.md](archive/reports/BANK_QUALITY_DESIGN_2026-08-01.md). 6 пунктов свипа сведены в **3 переиспользуемых примитива** (A head-aware кластер-по-морфеме: §1 серия-батчинг + §6 голова 蛊 · B ре-порядок `terminology.Batch`: §1 + feed_cap · C стеммер целевого письма: §3 Decl-шов + omission-бэкстоп чекеров) + дата-правки (тип — ОТДЕЛЬНЫМ классификатором первичным; род neuter enum+seed-lint, ось `RenderFormatVersion`). **Самопроверка — 3 рубежа (грунтинг + анкор-верификация всех 8 tech-debt-якорей CONFIRMED + 5-агентная адверсариальная дизайн-критика): §1 серия-дефиниция «равная длина/1 позиция» и §2 инлайн-тип были BROKEN — переработаны (§1 → head-aware, пере-верифицировано на `BANK-FULL.tsv`: наивное = 19 кластеров вкл. блоб с §2-harm+протагонист, head-aware = 4 годных серии; §2 → классификатор первичным, DO-роль ДО RenderBatch; $0-экран честно пере-скоупнут — провабельно пропускал юаньши/Юаньхай); §5/§6/§7-эмбеддинг WEAKENED вправлены.** §4 (36б второе мнение) и §7-эмбеддинг ЗАКРЫТЫ по нужности; §5 (резюме-слой 80) → пилот-Ф2.5-гейт (проза-суммарайзер закрыт; «дата-состояние свободно от D1» опровергнуто — source-anchored reveal без верификатора); §6-эмиссия головы → полигон-замер + после #10 (seed-половина строится). **First-occurrence-тест ИСПОЛНЕН** ($0, `eval/bank_autonomy/m_firstocc.py`): гипотеза первенства ОПРОВЕРГНУТА (финал==первое 7 == финал==частота 7, большинство — ни то ни другое) ⇒ роль = контекстный ре-решатель, усиливает §1 (со-батч — единственный рычаг). **7 вопросов владельцу** (§9: канон-пин между прогонами · замер 36б · диспозиция резюме · hard-инъективность · вкус авто-транслита реалий юаньци/цзин-ци-шэнь · короткая форма 27 · замер эмиссии головы). **СТОП — ратификация оркестратора/владельца; этап 2 (стройка) — по релею после лендинга пакета-чекеров.** → **РАТИФИЦИРОВАНО ВЛАДЕЛЬЦЕМ, ЭТАП 2 (СТРОЙКА) ИСПОЛНЕН (01.08, та же сессия, $0 — только фейк-провайдер в тестах, НЕ закоммичено, resnapshot НЕ запускался).** **Качество банка — этап 2 (стройка) исполнен.** Построены 3 примитива дизайна: **§1 серия-батчинг** (`terminology/series.go` `DetectSeries` head-aware + `Batch` серия-целиком + value-порядок feed_cap; пар-данные `lang.SeriesMorphology` из CJK-письма — инертно для алфавита; **пере-верифицировано на живом `BANK-FULL.tsv`: ровно 4 годных серии, ноль блоба**). **§2 тип-классификатор первичным** (новая фаза перед рендером на общем `runBankRoleBatches` — денежный цикл терминолога зарефакторен в общий, чекпойнт-хеш идентичен; корректный тип роутит conform + праймит RenderBatch + банкуется через `attachClassifiedType` БЕЗ смены эмиссии=без реоткрытия D39.50; своя роль/бюджет/промт `classifier.md`; честный $0 label-экран). **§3 Decl-шов** (`lang.TargetStemmer` примитив C + ru `decl_suffix` данные → `dstFormPresent` принимает косвенные падежи без перечисления форм; **neuter** enum→`injection.txt`/`genderConstraintNote`; **SeedLint** валидирует род громко; ось — **scoped `hasNeuter`-фолд** в `memory_version` по образцу `editor-unverified-section-v1`, НЕ бланкетный `RenderFormatVersion`). **Верификация (5 независимых сигналов):** `go test ./...` зелёно · `-race` чисто на затронутых пакетах · греп-общность: ноль пар/книго-литералов в Go-логике (новая пара — без правки Go) · голден Ш-1 **пере-захвачен, 172/172 hex-only = вердикт-нейтрально** (EmbeddedDataVersion сдвинулся от данных injection/target-ru) · **адверсариальное ревью author≠reviewer (4 измерения × refute-verify): 0 подтверждённых дефектов.** Короткая форма (27) — уже работает через `decl.forms[]`, кода не требует (§9-Q6 владельцу открыт). §4/§5/§6-эмиссия/§7-эмбеддинг — как в дизайне (не строятся/гейтнуты). diff --git a/docs/README.md b/docs/README.md index e866dc58..440db200 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,17 +11,17 @@ ## Структура -- `architecture/` — синтез. **Источник истины по решениям — [`05-decisions-log.md`](architecture/05-decisions-log.md) (D1–D39.74); при конфликте с любым доком он выше.** +- `architecture/` — синтез. **Источник истины по решениям — [`05-decisions-log.md`](architecture/05-decisions-log.md) (D1–D39.75); при конфликте с любым доком он выше.** - `01-decisions.md` — принципы Р1–Р10; `02-mvp-plan.md` — фазы и приёмка (v3, 09.07); `03-implementation-notes.md` — контракты Фазы 0; `04-unhappy-paths.md` — ~70 режимов отказа → механизм; `06-memory-risk-registry.md` — реестр рисков банка памяти; **[`09-target-architecture.md`](architecture/09-target-architecture.md) — целевая 7-слойная архитектура (D39; статус стройки — шапка-таблица; инвариант общности §0.1)** · [`10-prompt-architecture.md`](architecture/10-prompt-architecture.md) — консолидированная промпт-заметка (концерн 4) · [`12-go-style-notes.md`](architecture/12-go-style-notes.md) — норматив общности §0 + Go-ответы · [`13-tech-debt-anchors.md`](architecture/13-tech-debt-anchors.md) — якоря техдолга (справочник к бэклогу, НЕ трекер). **Исполненные (архив 25.07, санкция владельца, → `archive/architecture/`):** [`07-strategic-review.md`](archive/architecture/07-strategic-review.md) (стратаудит 09.07 — курс исполнен) · [`08-sync-audit-ledger.md`](archive/architecture/08-sync-audit-ledger.md) (ледджер синк-аудита — отработан) · [`11-implementation-plan.md`](archive/architecture/11-implementation-plan.md) (план пака-11 — исполнен целиком); `components.puml`/`pipeline.puml` — диаграммы (перерисованы 25.07 под пост-пак-16 реальность: волновой исполнитель, пакетный сплит пака-15, repair-петля за `enabled:false`, generic content-labels D39.25; владелец смотрит PlantUML-расширением VS Code; вручную НЕ рендерить). - `experiments/` — эмпирика «Полигона»: `00-provider-quirks` (читать перед любым вызовом провайдера), `01-token-calibration`, `02-refusal-benchmark`, `03-local-stand`, `04-editor-quality`, `06-local-extraction`, `07-coverage-precision`, `08-cost-model-v2` (актуальная денежная модель), `09-pilot-protocol` (пилот Ф2.5 + поправки D13), `10-explicit-benchmark` (18+ violence-рука канала B), `11-erotica-benchmark` (erotica по трём парам/регистрам — закрытие D14.4, D22), `12-quality-diagnosis`/`13-translator-bakeoff`/`14-quality-empirics`/`14b-meaning-battery` (дуга качества «мерить→строить», закрыты → D30/D32/D37/D38), `15-segmentation-empirics` (exp15: когезия под floor-шумом, фертильность — закрыт → D39.7/D39.8), `16-bank-mining` (exp16: WHICH/WHAT банк-майнинга — закрыт → D39.10). - `research/` — фактура исследований 04–05.07: `01–10` базовые, `11-gap-*` добор критиком, `12-*` режимы отказа/отзывы/таксономии (+ два внешних материала с провенанс-шапками), `13` валидация памяти, `14` адаптивная память, `15` голос и состояние (принят, D21), **`16` ридер-IDE (принят с ревью-шапкой, D29)**, **`17` внешняя критика GPT-5.6 (принят с ревью-шапкой, D25)** — у 16/17 читать шапку прежде тела. **`18` рычаги качества (два отчёта, D36)** · **`19` нарезка+когезия+контракт t/e (D39.1)** · **`20` банк-майнинг W1.5 (D39.6)** · **`21` обзор LLM-транспорта чужих харнессов (23.07: наш транспорт опережает/вровень со всеми 11)** · **`22` доменные харнессы перевода (24.07: калибрующий — ядро подтверждено, впереди COGS/18+/общность/измеренность, позади Q4-выпуск и gate+repair-петля; сиквел `02`/`05`)** — у всех ревью-шапки. ⚠ Часть под superseded-баннерами (01/02/03/04/05/09 и gap-1/2/5) — **читай баннер прежде содержимого**. - `PROGRESS.md` — **журнал** (CURRENT-STATE сверху, ниже хронология; НЕ источник решений). -- **Активные хендофф-промты (01.08; всё остальное — в `archive/prompts/` с баннерами-исходами):** [`ORCHESTRATOR_SESSION_PROMPT.md`](ORCHESTRATOR_SESSION_PROMPT.md) (роль/нормы; состояние НЕ дублирует) · [`BACKEND_BANK_FIXPACK_SESSION_PROMPT.md`](BACKEND_BANK_FIXPACK_SESSION_PROMPT.md) (**ТЕКУЩИЙ бэкендный, ПЕРВЫМ**: фикс-пак банка, строка 87) · [`BACKEND_CHECKER_PACKAGE_SESSION_PROMPT.md`](BACKEND_CHECKER_PACKAGE_SESSION_PROMPT.md) (пакет-чекеров против labels; ПОСЛЕ лендинга фикс-пака — общие файлы) · [`POLYGON_PACKAGE4_SESSION_PROMPT.md`](POLYGON_PACKAGE4_SESSION_PROMPT.md) (residual-трекер пилота/18+/echo, отложен; P4-хвост из него закрыт D39.61). Закрытые 30.07–01.08 (промты в `archive/prompts/`, отчёты с ревью-шапками в `archive/reports/`): пак-19 «голос/состояние» (D39.54–56) · холодный мини-прогон (D39.58) · ToS-речек (D39.57) · ресёрч общности фаза 1 (D39.60) · полигон Р6+P4 (D39.61) · **фаза 2 общности (D39.64)** · **полигон «сырьё арки банка»+пробы (D39.65)** · **качество банка, оба этапа (D39.69)**. +- **Активные хендофф-промты (01.08; всё остальное — в `archive/prompts/` с баннерами-исходами):** [`ORCHESTRATOR_SESSION_PROMPT.md`](ORCHESTRATOR_SESSION_PROMPT.md) (роль/нормы; состояние НЕ дублирует) · [`BACKEND_CHECKER_PACKAGE_SESSION_PROMPT.md`](BACKEND_CHECKER_PACKAGE_SESSION_PROMPT.md) (**ТЕКУЩИЙ бэкендный**: пакет-чекеров против labels + чистка ведра D — фикс-пак заленден, МОЖНО ЗАПУСКАТЬ) · [`POLYGON_PACKAGE4_SESSION_PROMPT.md`](POLYGON_PACKAGE4_SESSION_PROMPT.md) (residual-трекер пилота/18+/echo, отложен; P4-хвост из него закрыт D39.61). Закрытые 30.07–01.08 (промты в `archive/prompts/`, отчёты с ревью-шапками в `archive/reports/`): пак-19 «голос/состояние» (D39.54–56) · холодный мини-прогон (D39.58) · ToS-речек (D39.57) · ресёрч общности фаза 1 (D39.60) · полигон Р6+P4 (D39.61) · **фаза 2 общности (D39.64)** · **полигон «сырьё арки банка»+пробы (D39.65)** · **качество банка, оба этапа (D39.69)** · **фикс-пак банка (D39.75)**. - `archive/` — `prompts/`: закрытые сессионные промты (только история, инструкции оттуда не исполнять) · `reports/`: отчёты паков/ресёрч-сессий с ревью-шапками (живая фактура приёмок — на них ссылаются D-лог и активные промты) · `architecture/`: исполненные арх-доки (07 стратревью · 08 синк-ледджер · 11 план пака-11) · четыре слайса хроники `PROGRESS-2026-07-{04-10,10-13,13-25,25-31}.md`. -## Статус (2026-08-01, голова D39.74 — эра «общность → качество банка») +## Статус (2026-08-01, голова D39.75 — эра «общность → качество банка») -Полная карта состояния НЕ здесь: фазы/курс/очередь/горизонт — в шапке [`PROGRESS.md`](PROGRESS.md) (CURRENT-STATE + единый бэклог), решения — в [`architecture/05-decisions-log.md`](architecture/05-decisions-log.md) (карта актуальности; свежая голова — с хвоста файла). Коротко: Ф0/Ф1 ✅ · арх-ресет D39 (паки 11–16) ✅ · паки 17–20 ✅ · мини-прогон и холодный прогон приняты (D39.37/58) · карта языковой привязки движка снята (D39.60: книго-ось чиста; ja→ru безопасна без правки Go, en→ru — нет) · Р1–Р4 закрыты (D39.63) · пак-21 растворён (D39.62) · **фаза 2 общности принята (D39.64: цель/скрипт-швы по данным, эмбед-хеш, манифест-по-каналам; голден вердикт-нейтрален)**. Полигон-ветка закрыта D39.65 (строки 21/22, фиксы 36а/36в проверены живьём). Банк-пак принят целиком (D39.69); вопросы §9 закрыты (D39.70, канон целей — в CLAUDE.md). Текущее: фикс-пак банка ПЕРВЫМ (промт выдан D39.71) → пакет-чекеров после. Свип полноты исполнен и пост-сверен кодом (D39.66: 951 обязательство, потери возвращены; якоря — architecture/13). ⚠ DeepSeek-0731 сменил веса под слагом — платные прогоны СТОП до ре-пробы (бэклог-строка 74). Дальше: стройка банка ∥ пакет-чекеров → ре-проба flash → ДОБОР ИДЕАЛА (мини-прогон и свип гипотез влиты, D39.67) → вторая пара (ja→ru) → МАСШТАБ → пилот Ф2.5 → Ф3 ридер-IDE. +Полная карта состояния НЕ здесь: фазы/курс/очередь/горизонт — в шапке [`PROGRESS.md`](PROGRESS.md) (CURRENT-STATE + единый бэклог), решения — в [`architecture/05-decisions-log.md`](architecture/05-decisions-log.md) (карта актуальности; свежая голова — с хвоста файла). Коротко: Ф0/Ф1 ✅ · арх-ресет D39 (паки 11–16) ✅ · паки 17–20 ✅ · мини-прогон и холодный прогон приняты (D39.37/58) · карта языковой привязки движка снята (D39.60: книго-ось чиста; ja→ru безопасна без правки Go, en→ru — нет) · Р1–Р4 закрыты (D39.63) · пак-21 растворён (D39.62) · **фаза 2 общности принята (D39.64: цель/скрипт-швы по данным, эмбед-хеш, манифест-по-каналам; голден вердикт-нейтрален)**. Полигон-ветка закрыта D39.65 (строки 21/22, фиксы 36а/36в проверены живьём). Банк готов целиком (D39.69 ядро + D39.75 фикс-пак; вопросы §9 закрыты D39.70). Текущее: пакет-чекеров — можно запускать; строка 91 у владельца. Свип полноты исполнен и пост-сверен кодом (D39.66: 951 обязательство, потери возвращены; якоря — architecture/13). ⚠ DeepSeek-0731 сменил веса под слагом — платные прогоны СТОП до ре-пробы (бэклог-строка 74). Дальше: стройка банка ∥ пакет-чекеров → ре-проба flash → ДОБОР ИДЕАЛА (мини-прогон и свип гипотез влиты, D39.67) → вторая пара (ja→ru) → МАСШТАБ → пилот Ф2.5 → Ф3 ридер-IDE. ## Доступные ключи от моделей DEEPSEEK_API_KEY, ZAI_API_KEY, KIMI_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, XAI_API_KEY, MISTRAL_API_KEY diff --git a/docs/architecture/05-decisions-log.md b/docs/architecture/05-decisions-log.md index dc29232d..d0415435 100644 --- a/docs/architecture/05-decisions-log.md +++ b/docs/architecture/05-decisions-log.md @@ -1,4 +1,4 @@ -# Журнал решений оркестратора — контракт D1–D39.74 (развязки 04.07 · пакеты 09–10.07 · приёмка/качество-первым/пивот/эмпирика 11–12.07 · арх-ресет+стройка пере-прогонного стека 13–19.07) +# Журнал решений оркестратора — контракт D1–D39.75 (развязки 04.07 · пакеты 09–10.07 · приёмка/качество-первым/пивот/эмпирика 11–12.07 · арх-ресет+стройка пере-прогонного стека 13–19.07) > **⟶ КАРТА АКТУАЛЬНОСТИ (ревизия D31, продлена до D38.2 [12.07]; исторические записи ниже НЕ переписываются — дисциплина D23.3).** Читая контракт целиком, держи под рукой, что чем перекрыто: > ⚠ **Навигация (актуализация 01.08):** карта ниже детально покрывает D1–D39.28; решения D39.29–63 живут хронологически в теле файла, **свежая голова — С ХВОСТА** (новые ноты аппендятся вниз). Сводка текущей головы и очередь — CURRENT-STATE в `../PROGRESS.md`. @@ -1225,3 +1225,5 @@ API-529-долг закрыт: 8-осевой refute-by-default воркфлоу ## D39.73 — РЕКОНСИЛЯЦИЯ ТРИАЖА: ПОЛНАЯ МАШИННАЯ СВЕРКА НАХОДОК ПРОТИВ НОСИТЕЛЕЙ (владелец 02.08 «что ещё проебал? технические решения сессий проебал?»): все 77 находок свипа D39.66 + рекомендации пост-чека + фикс-листы приёмок D39.64–71 прогнаны механически против живых строк/дописок/закрытий. **Вердикт по решениям СЕССИЙ: целы** — их держал 951-позиционный свип (92% доказанных диспозиций) и адверсариальный пост-чек кодом; **провал локализован в ТРИАЖЕ оркестратора**: из ~95 рекомендаций уронено 5 (ведро D — уже исправлено D39.72; идиоматика-словари сехоуюй/васэй-эйго/вэньянь → дописка 52; Ф2-хвосты 02-mvp CometKiwi/T-index · Batch API судьи · llama-server · стриминг → строка 90 к пилот-препу; arity-полоса паспорта D29.2г → дописка 55; платный порог приёмки §2 «6/6 harm» жил только в гниющей шапке → дописка в строку 74 с полигон-слотами Q2/Q7). 7 из 8 греп-кандидатов оказались ложными (носители живы: порог-3 в 25 · lintNumberMagnitude в 79 · источник 74 · триггер 12 · линк 02-mvp починен · F3-brief в 71 · echo-R6 в PACKAGE4). Норма-урок: триаж многоисточниковых находок завершать МАШИННОЙ сверкой «каждая рекомендация → носитель», не глазами (вписано в промт-нормы оркестратора) (02.08, оркестратор №9). ✅ ## D39.74 — АУДИТ РЕСЁРЧЕЙ СОСЕДНИХ ХАРНЕССОВ (research/21 транспорт · research/22 домен; вопрос владельца 02.08 «есть ли утерянное из /home/ubuntu/projects/tmp?»): **утерянного НЕТ** — из 15 клонов оба ресёрча покрыли корпус, взятое ПОСТРОЕНО (research/21: 9 транспорт-пунктов паками 12/13 — quota-429/Retry-After/keepalive/джиттер/xAI-гард/GLM-finish/retryable-пин/billed-decode-кап/models-валидация + CostSource и луп-гард; вердикт «наш транспорт опережает/вровень со всеми 11, не трогаем» ратифицирован; research/22: разрыв Q6 gating+repair закрыт паком-16 по прецеденту AiNiee ProofreadTask, Problem-классы №5 → строка 25, планы → строка 71), отклонённое отклонено ЗАПИСЬЮ, кандидаты Q7-квирков ждут вендор-сверки по правилу 10.07 (не абсорбированы — так и задумано). Два хвоста, уроненные МОИМ триажем внутри пучковых находок (класс D39.73), добиты: **(1) Mistral-strip гейт message-builder** (research/21 §1.1: extra_forbidden на reasoning-поля; ЛАТЕНТЕН — наш wire single-shot без reasoning-полей; станет несущим при multi-turn/continuation или прод-пути mistral в канале B) → дописка в строку 59 (транспорт-гигиена); **(2) halve-chunk-on-retry** (research/22 §2 №7) → ОТКЛОНЁН ЯВНО: волновая архитектура с чекпойнтами + флор max_tokens + ре-ген-ручка 77 делают полу-чанк малоценным; реопен — при живых max_tokens-упорах (строка 45, дописка) (02.08, оркестратор №9). ✅ + +## D39.75 — ФИКС-ПАК БАНКА ПРИНЯТ И ЗАЛЕНДЕН (все 9 пунктов строки 87 отработаны; приёмка исполнением + 3-агентное адверсариальное ревью: 14/15 CONFIRMED · 1 PARTIAL · 0 REFUTED; голден независимо: маскированный дифф ПУСТ, двинулся только `embedded_version`): **СДЕЛАНО** — (б) head-finality В ДАННЫХ (`script-series.txt` + `SeriesHeadFinal`; zh/ja/ko функционально идентичны — пин-тест; синтетический head-initial скрипт заводится данными) · (в) лживый комментарий Batch переписан на правду (ChunkIdx фолдится в RequestHash) + `BatchRunes`-WARN на оверсайз-серию (НЕ режет — резка убила бы со-батч) · (г) `classify_types` при выключенном гейте = fail-loud + 5 config-тестов · (д) мутационные тесты neuter-фолда (версия двигается; mined-scope: BASE байт-идентичен — draft-волна не перепокупает) · (ж) дистилл-фикстура BANK-FULL (150 поверхностей побайтно = боевому TSV; тест требует ТОЧНОГО членства 4 серий и id=0 для 方源/元石/元海) · (и) `TokenizeWords`→`text.TokenizeLetters` — консолидация чистая (слои однонаправленны) И ПОБОЧНО ЗАКРЫЛА РЕАЛЬНУЮ ЩЕЛЬ: старый токенизатор рвал слово на mid-word U+0301 («сло́во»→[сло во] — воспроизведено на копии старого кода), decl-пост-чек промахивался на ударениях. **НЕ ДЕЛАТЬ с контрактом в код** — (е) стеммер НЕ заводится в voice (направления несимметричны: SelfRef-стемминг безопасен, NG-стемминг = шум; контракт-блок voice.go:368-378, фактура сверена) · (з) Q5-инструкция реалий УЖЕ в `terminologist.md`/`classifier.md` — причём с D39.45 (мисатрибуция отчёта «D39.58» только усиливает клейм), PromptSHA256 не двигался. **(а) ОТКАЧЕН — МАНДАТ САМОПРОВЕРКИ СРАБОТАЛ ПО НАЗНАЧЕНИЮ:** «ь» в `decl_suffix` закрыл бы «Фан Юаню», но неотличим по форме от коллизии мягкознаковых имён с лексикой («Синь»↔«синий/синим» — контрфактика воспроизведена: SameStem=true с «ь»); адверсариальный ревьюер сессии поймал ДО лендинга, откат + два регресс-гвоздя (SameStem + dstFormPresent; пара Линь/линию инертна — заменить на Линь/линий при касании) + NB-комментарий в данных. **Finding-1 ОСТАЁТСЯ ОТКРЫТЫМ → строка 91 (выбор владельца):** приёмка склонений мягкознаковых имён стеммером НЕВОЗМОЖНА — (а) сид/терминолог перечисляет `decl.forms[]` для -нь/-ль имён (естественно ложится на Decl-эмиссию) или (б) anchor-gated матч; + прямой тест-гвоздь на дизайн-кейс добавить при решении. Граница плотности (`enabled` серий через Go-константу `cjkScriptNames`) — пре-существующая, дописана в строку 79. Строка 87 ЗАКРЫТА (02.08, оркестратор №9). ✅ diff --git a/docs/BACKEND_BANK_FIXPACK_SESSION_PROMPT.md b/docs/archive/prompts/BACKEND_BANK_FIXPACK_SESSION_PROMPT.md similarity index 89% rename from docs/BACKEND_BANK_FIXPACK_SESSION_PROMPT.md rename to docs/archive/prompts/BACKEND_BANK_FIXPACK_SESSION_PROMPT.md index 198b97ab..668026be 100644 --- a/docs/BACKEND_BANK_FIXPACK_SESSION_PROMPT.md +++ b/docs/archive/prompts/BACKEND_BANK_FIXPACK_SESSION_PROMPT.md @@ -1,8 +1,8 @@ # Промт бэкенд-сессии: ФИКС-ПАК БАНКА (строка 87 + Q5; $0-код, короткий) -**Выдан 02.08.2026, оркестратор №9 (D39.71); санкция владельца («передать записку тем же бэкендерам»).** Ты — бэкенд-сессия TextMachine, продолжение банк-пака D39.69 (если ты свежий инстанс — онбординг: `CLAUDE.md` вкл. НОВЫЙ канон «Цели и мерило» → CURRENT-STATE `docs/PROGRESS.md` → ревью-шапка `docs/archive/reports/BANK_QUALITY_DESIGN_2026-08-01.md` — там все якоря находок). Зона: `backend/` + отчёт. НЕ коммитить. **Первый деливерабл — эхо-блок ≤8 строк** (первым сообщением; подтверждения не жди). +> **⚠ АРХИВ (02.08.2026): исполнен и ПРИНЯТ — D39.75** (9/9 пунктов; (а) откачен по делу — finding-1 → строка 91). Отчёт: [`../reports/BANK_FIXPACK_2026-08-02.md`](../reports/BANK_FIXPACK_2026-08-02.md). Файл несёт живые правки владельца. Инструкции НЕ исполнять. -⚠ **$0** (развилка 74). ⚠ **Очерёдность:** этот фикс-пак идёт ПЕРВЫМ; пакет-чекеров стартует после его лендинга (общие файлы membank/prompts). Если в дереве уже есть чужие правки `backend/` — СТОП и пинг. `TM_UPDATE_GOLDEN` — один раз в конце. +**Выдан 02.08.2026, оркестратор №9 (D39.71); санкция владельца («передать записку тем же бэкендерам»).** Ты — бэкенд-сессия TextMachine, продолжение банк-пака D39.69 (если ты свежий инстанс — онбординг: `CLAUDE.md` вкл. НОВЫЙ канон «Цели и мерило» → CURRENT-STATE `docs/PROGRESS.md` → ревью-шапка `docs/archive/reports/BANK_QUALITY_DESIGN_2026-08-01.md` — там все якоря находок). Зона: `backend/` + отчёт. НЕ коммитить. **Первый деливерабл — эхо-блок ≤8 строк** (первым сообщением; подтверждения не жди). ## Состав (= бэклог-строка 87; каждому пункту — краткая вердикт-тройка по мандату D39.68, «не делать с обоснованием» — легитимный исход для (в)/(е)/(и)) @@ -18,6 +18,8 @@ ## Приёмка (короткая, но обязательная — «ВСЕГДА на важных») -`go build`/`vet`/`go test -race ./...` зелёные В ФИНАЛЕ · майнер-парити EXACT · голден: пере-захват ОДИН раз в конце, маскированный дифф, отчитать оси (двинутся `embedded_version` от (а)/(б)-данных и `PromptSHA256` от (з) — вердикты двигаться НЕ должны; если двинулись — вскрытые единицы и обоснование) · греп-общность своего диффа · адверсариальное селф-ревью исполнением. +`go build`/`vet`/`go test -race ./...` зелёные В ФИНАЛЕ · майнер-парити EXACT · голден: пере-захват ОДИН раз в конце, маскированный дифф, отчитать оси (двинутся `embedded_version` от (а)/(б)-данных и `PromptSHA256` от (з) — вердикты двигаться НЕ должны; если двинулись — вскрытые единицы и обоснование) · греп-общность своего диффа. + +По окончании: селф ревью на алгоритмическую правильность, на алгоритмическое соотвествие стандартам репозитрия и правильность предложенных заранее фиксов. **Отчёт:** `docs/archive/reports/BANK_FIXPACK_<дата>.md` (компактный: тройки · метрики · голден-протокол) + итог 3–5 строк в §Бэкенд `docs/PROGRESS.md` (таблицу не трогать). Расхождение промта с кодом = пинг. **СТОП — приёмка оркестратора.** diff --git a/docs/archive/reports/BANK_FIXPACK_2026-08-02.md b/docs/archive/reports/BANK_FIXPACK_2026-08-02.md new file mode 100644 index 00000000..cd1e6a3d --- /dev/null +++ b/docs/archive/reports/BANK_FIXPACK_2026-08-02.md @@ -0,0 +1,53 @@ +# Отчёт: ФИКС-ПАК БАНКА (бэклог-строка 87 + Q5) — D39.71 + +> **✅ ПРИНЯТ И ЗАЛЕНДЕН (оркестратор №9, 02.08.2026, D39.75).** Приёмка исполнением: `build`/`vet`/`-race` все пакеты · майнер-парити EXACT · голден сверен независимо (86/86 строк, маскированный дифф ПУСТ; двинулся только `embedded_version` — `memory_version`/`prompt_sha256`/`content_hash`/все вердикты побайтно на месте) · 3-агентное адверсариальное ревью: **14/15 клеймов CONFIRMED, 1 PARTIAL, 0 REFUTED**. Ключевое подтверждено контрфактикой: с возвращённым «ь» `SameStem("Синь","синим")=true` — откат (а) верен, регресс-гвозди работают (3 из 4 пар; пара Линь/линию инертна — заменить на Линь/линий при касании); (з) даже сильнее заявленного — инструкция реалий в промптах с D39.45, не D39.58. PARTIAL: дизайн-кейс «Фан Юаню»→«Фан Юань» закреплён только комментарием + анти-коллизионным ассертом — прямой гвоздь добавить при решении finding-1 (строка 91). Combmark-щель (и) воспроизведена живьём на копии старого кода («сло́во»→[сло во]) и закрыта. Ниты: тихий head-final-дефолт незаявленного скрипта (осознанно, все 4 текущих объявлены) · BatchRunes лёгкий overcount (+2/блок, самосогласован). Finding-1 → строка 91 (выбор владельца); граница плотности `cjkScriptNames` → дописка 79. Строка 87 ЗАКРЫТА. + +**Дата:** 02.08.2026 · **Сессия:** бэкенд (продолжение банк-пака D39.68/69) · **Зона:** `backend/` + этот отчёт · **Деньги:** $0 (ни одного платного вызова; `--resnapshot`/`--resnapshot`-эквивалент не запускался; голден пере-захвачен фейк-провайдером) · **Статус:** НЕ закоммичено — лендит оркестратор. + +## Вердикт-тройки (по мандату D39.68; «не делать с обоснованием» — легитимно для в/е/и) + +| # | Пункт | Вердикт | Что сделано / обоснование | +|---|---|---|---| +| **а** | `decl_suffix` +«ь», палладиевский класс | **ОТКАЧЕНО — предложенный фикс ФЛАВЕД (адверс. находка) → ПИНГ** | Bare «ь» был добавлен, затем ОТКАЧЕН: адверсариальное ревью (подтверждено white-box) нашло, что стрип «ь» неотличим по форме от ВРЕДНОЙ коллизии. «юань»→«юан» (нужный выигрыш) и «синь»→«син» (коллизия) — идентичная форма 4-рунного мягкознакового имени→3-рунный стем, ни одно min-stem-правило их не разделяет. Топ-фамилии Палладия «Синь/Линь» (辛/信/新, 林) схлопываются на «син/лин» = стемы общих слов «синий/линия»: `Bank.SpoilerLeaks` на отклонённой «Синь» выдаёт ЛОЖНЫЙ спойлер-лик на любом «синий» (направление ШУМА, против precision-дисциплины); `Postcheck` маскирует дроп «Синь» на любом «синем» (safe-направление, но частота много выше допущения D24.4 — «имена не рекуррируют случайно»; тут рекуррирует ОБЩЕЕ слово). Симметрия «нь/ль/й/ью» проанализирована, но нерелевантна — корень в самом «ь». РЕШЕНИЕ: не тащить коллизию; NB-коммент в `target-ru.txt` + регресс-гвоздь `TestSoftSignNamesDoNotCollideWithCommonWords` (падает, если «ь» вернут). Finding 1 (палладий) остаётся ОТКРЫТЫМ в safe-направлении — правильный путь пингом. | +| **б** | `head_final` → данные | **СДЕЛАНО** | head-finality вынесена из Go-константы в `data/script-series.txt` (`head_positionscriptfinal\|initial`). `SeriesMorphology` резолвит head-final через `SeriesHeadFinal(script)` (данные; дефолт для незаявленного плотного скрипта = final, CJK-норма). Плотность (`enabled`) по-прежнему `cjkScriptNames` — предсуществующая граница общности, НЕ трогалась (пинг ниже). Тест: `TestSeriesMorphologyUnchanged` (zh/ja/ko→true,true; en/ru/unknown→false,false) + `TestSeriesHeadFinalIsDataDriven` (синтетический `initial`-скрипт резолвится head-initial без правки Go; corrupt-строки — loud). | +| **в** | Лживый коммент Batch + оверсайз-серия | **СДЕЛАНО (WARN)** | Коммент Batch переписан на правду: порядок ДЕТЕРМИНИРОВАН (стабильная сортировка по чистой функции контента) → $0-resume держится, НО ординал батча фолдится в request-hash (потребитель адресует batch i как ChunkIdx i), значит хеш НЕ независим от порядка (смена частот → сдвиг ординалов → перепокупка затронутых батчей). Оверсайз-серия: вердикт **WARN, не резать** (резать серию убивает со-батчинг §1; тихо оставить — против дисциплины «no silent degradation»). Добавлен `terminology.BatchRunes` + WARN в терминологисте, называющий over-cap-юнит (мина cap-8000). Тест: `TestBatchRunesFlagsOversize`. | +| **г** | `classify_types` при выкл. гейте | **СДЕЛАНО** | При `enabled:false, classify_types:true` — раньше ТИХИЙ no-op (`runClassifier` достижим только через включённый гейт, и валидация была вложена в `if tg.Enabled`). Теперь `else if tg.ClassifyTypes → bad(...)` (fail-loud, класс «гейт, который не может выстрелить»). Тесты: valid-load · нет classifier.md · budget≤0 · неизвестная classify_model · **выкл.гейт+classify_types** (`terminology_classify_test.go`). | +| **д** | Мутационный тест neuter-фолда | **СДЕЛАНО** | `memneuter_test.go` по образцу memvoice-пина: `TestNeuterRowMovesTheBankVersion` (строка gender:neuter двигает memory_version) + `TestNeuterFoldRespectsTheMinedScope` (mined-only neuter фолдится в enriched, НЕ в base — draft-волна не перепокупает; гвоздит порядок `hasNeuter`-после-`continue`). Честная оговорка в комментарии теста: тег `neuter-directive-v1` НЕ black-box-изолируем от уже-фолдящегося поля gender — тесты пинят наблюдаемый контракт. | +| **е** | Voice-checker × стеммер | **НЕ ДЕЛАТЬ (контракт в код)** | Стеммер НЕ заводится в voice-канал. Обоснование (записано в `voice.go`): имена уже приходят склонённо-осведомлёнными (`Forms` = база + перечисленные decl-формы, тот же `decl.forms[]`); accept-biased стеммер поверх добавил бы лишь риск стем-коллизий/мис-атрибуции чекеру с доктриной precision-over-recall. Для SelfRef/NG стеммер НЕ единонаправленно безопасен: стем SelfRef подавил бы ложный self-ref-флаг (безопасно), но стем NG РАСШИРИЛ бы матч запретной леммы (шум) — противоположные направления на одном чекере. Склонение SelfRef/NG — забота ДРАЙВЕРА (перечислять формы из сида, как уже Forms), не match-time стеммера. | +| **ж** | Дистилл-фикстура BANK-FULL | **СДЕЛАНО** | Поверхности `books/gu-zhenren/coldrun-a/BANK-FULL.tsv` (150) → `terminology/testdata/bankfull-surfaces.txt` (пар тест-данные). `TestDetectSeriesOnBankFullFixtureIsStable` пинит ТОЧНО 4 серии по реальному голова-скелету: 转-ранг {一二三四五六九转} (7), 等-грейд {甲乙丙丁等} (4), 等资质 {甲乙丙等资质} (3), 阶-тир {初中高阶} (3). «Ноль блоба» = каждая серия РОВНО равна ожидаемой группе (лишний член = провал), а не эвристика «≤N членов» (легитимная 7-членная ранг-серия — не блоб). 方源/元石/元海 вне. | +| **з** | Q5 — реалии в пар-промпты | **НЕ ДЕЛАТЬ (уже есть) → ПИНГ** | Инструкция «реалии по смыслу; транслитерация — именам» УЖЕ присутствует в `terminologist.md` §26-27 (закоммичено с D39.58) и `classifier.md` §10-27 (с harm-примером 元石→«юаньши»). Это ровно те два реалия-рендерящих промта, что НЕ снапшот-фолдятся (терминология-гейт) → «перекупок нет» уже соблюдено. Переводчик/translator-banknote рендерят реалии инлайн, НО их промт снапшот-фолдится (draft-стадия) → правка перекупила бы draft-волну, против «перекупок нет» → исключены сознательно. Итог: `PromptSHA256` НЕ двинулся (голден подтверждает). | +| **и** | Дубль токенизации | **СДЕЛАНО (консолидация)** | `lang.TokenizeWords` теперь делегирует в новый `text.TokenizeLetters` (script-agnostic, combining-mark-aware), разделяющий ядро с `text.TokenizeScript`. Слой соблюдён: `lang` импортирует `text` (не наоборот; цикла нет — сборка/vet зелёные). Побочно чинит combining-mark щель в decl-пост-чеке (стресс-акцент больше не рвёт слово). Тест: `text.TestTokenizeLetters` (в т.ч. совпадение с TokenizeScript на односкриптном тексте). | + +## Метрики / приёмка + +- `go build ./...` · `go vet ./...` — **зелёные**. +- `go test -race ./...` — **зелёные, все пакеты** (в т.ч. `internal/miner` майнер-парити EXACT, `internal/pipeline` голден). +- Новые тесты: lang (2 функции series + soft-sign регресс-гвоздь), config (5 classify-веток), membank (2 neuter + анти-коллизия «Синь»), terminology (BANK-FULL фикстура + BatchRunes), text (TokenizeLetters). + +## Голден-протокол (пере-захват ОДИН раз в конце) + +- `TM_UPDATE_GOLDEN=1 go test ./internal/pipeline/ -run TestGoldenDeterminism` — 86 строк изменилось. +- Маскированный дифф (`sed 's/^[+-]//; s/[0-9a-f]{12,}/HEX/g' | sort | uniq -u`) — **ПУСТ**: каждое изменение отличается ТОЛЬКО в hex-токенах → вердикт-нейтрально. +- Оси (HEAD vs new, точечная сверка; голден пере-захвачен ДВАЖДЫ — второй раз после отката «ь», финальный авторитетный): + - `embedded_version`: `embed-v1-37bf9699835f` → `embed-v1-3df4329e3b9e` — **ЕДИНСТВЕННЫЙ корень** (NB-коммент `target-ru.txt` (а) + новый `script-series.txt` (б); функциональных данных «ь» в файле НЕТ после отката). + - Каскад: `snapshot_draft`/`snapshot_edit`, все request-хеши (`final_hash` = «request_hash авторитетного чекпойнта», `chunkstatus.go:31` — АДРЕС, не хеш текста; request-log `hash=`) — сдвинулись как адресация, фолдящая снапшот. + - **НЕ двинулись**: `memory_version`, `content_hash`, `prompt_sha256` (⇒ (з) не правил промт), `render_format_version`, и ВСЕ dispositions/flags/finish/degraded/cost/tokens. +- Общность: греп ДОБАВЛЕННЫХ engine-Go-строк (без комментов/тестов/данных) на CJK/пар/книго-литералы — **пусто**. Движок остаётся общим (пара не-из-репо заведётся без правки Go; head-position — данные). + +## Самопроверка исполнением + адверсариальное ревью (author≠reviewer) + +Самопроверка (алгоритмы · соответствие стандартам репо · правильность предложенных фиксов): соло-проход пере-верифицировал каждый пункт исполнением (тесты · маскированный голден · греп-общность · точечная сверка осей) и не нашёл дефектов сам. + +Адверсариальное ревью (Workflow `wf_fea3802b-5a1`: 5 refute-biased ревьюеров по измерениям × refute-verify; 7 агентов, 0 ошибок): +- **1 ПОДТВЕРЖДЁННАЯ находка (medium) — акцептирована и ИСПРАВЛЕНА:** коллизия bare «ь» (пункт (а) выше). Ревьюер репродуцировал white-box против реального кода; я независимо пере-верифицировал (`SameStem("Синь","синим")=true`, `dstFormPresent(«Синь», …синим…)=true`; до диффа — false). Действие: откат «ь» + регресс-гвоздь + пинг. ЭТО и есть ценность мандата само-проверки — флавед-фикс пойман ДО лендинга. +- **1 ОТВЕРГНУТА (refuted):** критика комментария об исключении «й» — верно по механике (пре-существующий safe-направленный miss на не-гейтящем флаггере), но НЕ дефект, вносимый диффом; исключение «й» — осознанное задокументированное решение (и «й» всё равно не закрыл бы «Юаньхай» из-за коллизии с «-ая»). +- **4 из 5 измерений (б·в+и·г+д·общность/ж/е/з): findings пусты** — 0 дефектов. + +## Пинги оркестратору (расхождение промта с кодом · открытые решения) + +1. **(а) finding 1 НЕ закрыт — нужен НЕ-стеммерный механизм (РЕШЕНИЕ владельца).** Предложенный «ь» флавед (см. выше). Палладиевский мягкознаковый gap («Фан Юаню»~«Фан Юань») требует одного из: **(1) сид-перечисление decl-форм** для мягкознаковых имён собственных (тот же драйвер-путь, что записан для (е) — точный матч, коллизия невозможна; вне бэкенд-скоупа, задача полигона/сида); либо **(2) anchor-gated soft-final матч** — новая ДАННЫЕ-категория мягких окончаний + правило «декл-матч требует ≥1 ТОЧНО совпавшего компонента-якоря», так что одиночные «Синь» без якоря не матчатся, а «Фан Юань» матчится через якорь «Фан». Вариант (2) — новое ДИЗАЙН-решение (по моему уроку — валидировать на корпусе и ратифицировать, не совать в фикс-пак реактивно). Прошу выбор владельца. +2. **(з) уже сделано:** Q5-инструкция реалий уже в `terminologist.md` §26-27 (D39.58)/`classifier.md` — фикс-пак предполагал её отсутствие. Правки не требуется; `PromptSHA256` не двинулся (в отличие от ожидания приёмки). +3. **(б) граница общности:** данными вынесена ТОЛЬКО head-finality; `enabled` (плотность) по-прежнему piggyback на Go-константе `cjkScriptNames` — новый плотный скрипт всё ещё требует Go-строки там (предсуществующая граница, вне (б)-скоупа). Полной дата-общности серий — отдельная задача, если нужна. +4. **`docs/BACKEND_BANK_FIXPACK_SESSION_PROMPT.md`** в рабочем дереве несёт ЖИВУЮ правку владельца (снят «$0/очерёдность»-абзац, переписана строка приёмки на «селф ревью») — НЕ моя, не трогал (чужой незакоммиченный файл). + +**СТОП — приёмка оркестратора.**