Land pre-rerun-prep (D39.19): CLI exit code for signature-stop, mining reject-set, x/text version fold into Sh-2, echo_draft/edit split, read-model unit naming, and nit cleanup; golden version-only, parity exact, section-8 manifest complete
This commit is contained in:
parent
ac5f312834
commit
a1f2aa981e
28 changed files with 520 additions and 336 deletions
|
|
@ -107,4 +107,17 @@ func TestExitCodeContract(t *testing.T) {
|
|||
if exitCode(fmt.Errorf("outer: %w", sentinel)) != 2 {
|
||||
t.Fatal("wrapped sentinel → 2")
|
||||
}
|
||||
// R1-FL-A: a bank-mining signature stop is a DISTINCT exit code (3), not conflated with a flag (2) or a
|
||||
// crash (1), so exit-code automation can tell a sign-boundary pause from a real failure.
|
||||
sigStop := &pipeline.WaveSignatureStop{Terms: 4, SignaturePath: "/tmp/book.db.mined-signature.yaml"}
|
||||
if exitCode(sigStop) != 3 {
|
||||
t.Fatalf("signature-stop sentinel → 3, got %d", exitCode(sigStop))
|
||||
}
|
||||
if exitCode(fmt.Errorf("outer: %w", sigStop)) != 3 {
|
||||
t.Fatal("wrapped signature-stop → 3")
|
||||
}
|
||||
// The two sentinels must NOT collide: a flag stays 2 even though both are typed sentinels.
|
||||
if exitCode(sentinel) == exitCode(sigStop) {
|
||||
t.Fatal("CompletedWithFlags and WaveSignatureStop must map to different exit codes")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,16 +26,23 @@ func main() {
|
|||
os.Exit(exitCode(err))
|
||||
}
|
||||
|
||||
// exitCode maps a run() error onto the ratified shell contract (Веха 2): 0 clean ·
|
||||
// 2 completed-with-flags (приёмка допускает N флагов; типизированный сентинел
|
||||
// *pipeline.CompletedWithFlags, переживает %w-обёртки через errors.As) · 1 infra
|
||||
// failure и всё остальное, включая ошибку разбора флагов (ContinueOnError в
|
||||
// parseInvocation держит 2 эксклюзивным для флагнутых чанков).
|
||||
// exitCode maps a run() error onto the ratified shell contract (Веха 2 / R1-FL-A): 0 clean · 2
|
||||
// completed-with-flags (приёмка допускает N флагов; типизированный сентинел
|
||||
// *pipeline.CompletedWithFlags, переживает %w-обёртки через errors.As) · 3 bank-mining
|
||||
// signature stop (*pipeline.WaveSignatureStop — the run paused before the edit wave for owner
|
||||
// sign; a DELIBERATE human-in-the-loop halt, not a crash) · 1 infra failure и всё остальное,
|
||||
// включая ошибку разбора флагов. 3 is DISTINCT from both 2 (flagged chunks) and 1 (crash) so
|
||||
// exit-code automation can tell a sign-boundary pause from a real failure — a human sees the
|
||||
// stderr text either way, but a script that shipped "translate && export" must NOT treat the
|
||||
// pause as success (exit 0) nor as an infra crash (exit 1).
|
||||
func exitCode(err error) int {
|
||||
var flagged *pipeline.CompletedWithFlags
|
||||
var sigStop *pipeline.WaveSignatureStop
|
||||
switch {
|
||||
case err == nil:
|
||||
return 0
|
||||
case errors.As(err, &sigStop):
|
||||
return 3
|
||||
case errors.As(err, &flagged):
|
||||
return 2
|
||||
default:
|
||||
|
|
@ -89,6 +96,13 @@ func translate(ctx context.Context, cfgPath string, resnapshot bool) error {
|
|||
|
||||
res, err := r.TranslateBook(ctx)
|
||||
if err != nil {
|
||||
// R1-FL-A: the bank-mining signature stop is a typed sentinel, not an infra failure — render the
|
||||
// operator's next steps to stdout (terms + signature-map path + resume guidance) and return it so
|
||||
// main() maps it to the distinct exit code 3 (the Error() text also prints to stderr).
|
||||
var sigStop *pipeline.WaveSignatureStop
|
||||
if errors.As(err, &sigStop) {
|
||||
renderSignatureStop(os.Stdout, sigStop)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return renderTranslate(os.Stdout, res, func() (float64, float64, error) {
|
||||
|
|
|
|||
|
|
@ -70,6 +70,19 @@ func renderTranslate(w io.Writer, res *pipeline.BookResult, ledger func() (commi
|
|||
return nil
|
||||
}
|
||||
|
||||
// renderSignatureStop prints the operator-facing bank-mining signature-stop banner (R1-FL-A): the run
|
||||
// paused before the edit wave because the miner proposed N new terms for owner sign. It surfaces the term
|
||||
// count + the signature-map path + the resume path (promote a term into the mined-delta file OR decline it
|
||||
// in the mined-rejects file, then re-run). Distinct from a flag/crash — main() maps the sentinel to exit 3.
|
||||
func renderSignatureStop(w io.Writer, s *pipeline.WaveSignatureStop) {
|
||||
fmt.Fprintf(w, "=== БАНК-МАЙНИНГ: СТОП НА ПОДПИСЬ (%d новых терм(ов)) ===\n", s.Terms)
|
||||
fmt.Fprintf(w, "Прогон остановлен ПЕРЕД волной редактуры: майнер предложил %d новых терм(ов) на подпись владельца.\n", s.Terms)
|
||||
fmt.Fprintf(w, "Карта подписи: %s\n", s.SignaturePath)
|
||||
fmt.Fprintln(w, "Дальше: по каждому терму ЛИБО промоутни его в mined-delta файл (approved + dst), ЛИБО отклони в")
|
||||
fmt.Fprintln(w, "mined-rejects файле (book.yaml: mined_rejects), затем перезапусти `tmctl translate`. Стоп очистится,")
|
||||
fmt.Fprintln(w, "когда каждый предложенный терм промоутнут или отклонён (тогда дельта пуста → авто-продолжение к редактуре).")
|
||||
}
|
||||
|
||||
// flagSuffix renders a non-empty flag reason as "(reason)".
|
||||
func flagSuffix(r pipeline.FlagReason) string {
|
||||
if r == "" {
|
||||
|
|
@ -217,14 +230,16 @@ func errTail(degraded, errText string) string {
|
|||
// function's frozen callback contract (render_test.go) is untouched.
|
||||
func renderQuality(w io.Writer, q *pipeline.QualityReport) error {
|
||||
fmt.Fprintf(w, "\n=== КАЧЕСТВО (per-run, детерминированные сигналы — наблюдаемость, не гейт) ===\n")
|
||||
fmt.Fprintf(w, "чанков всего=%d · дошло до финала=%d · с экспорт-текстом=%d\n", q.TotalChunks, q.ProcessedChunks, q.TextChunks)
|
||||
fmt.Fprintf(w, "единиц всего=%d · дошло до финала=%d · с экспорт-текстом=%d\n", q.TotalUnits, q.ProcessedUnits, q.TextUnits)
|
||||
// Claim-1: рубленые абзацы. ≈1.0 предл./абзац = рублено (претензия владельца); выше = слитая проза.
|
||||
fmt.Fprintf(w, "СТРУКТУРА (претензия-1 «рубленые абзацы»): предл/нарратив-абзац=%.2f (предложений=%d / нарратив-абзацев=%d)\n",
|
||||
q.MeanSentPerNarrPara, q.NarrativeSentences, q.NarrativeParagraphs)
|
||||
fmt.Fprintf(w, "СИГНАЛЫ: диалог-тире=%d · глоссарий-промахов=%d · число-дрейф=%d · trust-gated(сид)=%d\n",
|
||||
q.DialogueDashFlags, q.GlossaryMisses, q.NumberDriftFlags, q.TrustGated)
|
||||
fmt.Fprintf(w, "СТРИПЫ/УТЕЧКИ: косметик-strip чанков=%d (%.1f%%, markdown+CJK) · echo(cjk_artifact) чанков=%d (%.1f%%)\n",
|
||||
q.CosmeticStripChunks, 100*q.CosmeticStripRate, q.EchoChunks, 100*q.EchoRate)
|
||||
// Echo split (D39.18): draft = переводчик эхнул (вкл. дропнутых c-lite членов) / edit = редактор эхнул в
|
||||
// доставленном. Косметик-strip — по единицам (санитайзер бежит на финальной стадии).
|
||||
fmt.Fprintf(w, "СТРИПЫ/ЭХО: косметик-strip единиц=%d (%.1f%%, markdown+CJK) · echo черновик=%d (%.1f%%) · echo редактура=%d (%.1f%%)\n",
|
||||
q.CosmeticStripUnits, 100*q.CosmeticStripRate, q.EchoDraftChunks, 100*q.EchoDraftRate, q.EchoEditUnits, 100*q.EchoEditRate)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -238,8 +253,8 @@ func renderExport(w io.Writer, exp *pipeline.BookExport, asPlaintext bool) error
|
|||
if asPlaintext {
|
||||
// Manifest/drift summary first (F3/F4): a partial or drifted book is EXPLICIT, not silently
|
||||
// exported as complete.
|
||||
fmt.Fprintf(w, "# export %s — чанков всего=%d, экспортировано=%d, ожидают=%d",
|
||||
exp.BookID, exp.TotalChunks, exp.TotalChunks-exp.PendingChunks, exp.PendingChunks)
|
||||
fmt.Fprintf(w, "# export %s — единиц всего=%d, экспортировано=%d, ожидают=%d",
|
||||
exp.BookID, exp.TotalUnits, exp.TotalUnits-exp.PendingUnits, exp.PendingUnits)
|
||||
if exp.GhostRows > 0 {
|
||||
fmt.Fprintf(w, ", ghost-строк-отброшено=%d", exp.GhostRows)
|
||||
}
|
||||
|
|
@ -292,7 +307,7 @@ func renderStatusJSON(w io.Writer, rep *pipeline.StatusReport) error {
|
|||
// (its message goes to stderr in main), so a CI/IDE consumer both parses the projection AND
|
||||
// sees "attention needed" in the exit code instead of silently reading 0.
|
||||
if rep.Flagged > 0 {
|
||||
return &pipeline.CompletedWithFlags{Flagged: rep.Flagged, Total: rep.TotalChunks}
|
||||
return &pipeline.CompletedWithFlags{Flagged: rep.Flagged, Total: rep.TotalUnits}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -315,8 +330,8 @@ func renderStatusHuman(w io.Writer, rep *pipeline.StatusReport, cfgPath string)
|
|||
drift += " ⚠ CONFIG-DRIFT (текущий конфиг рендерит другой снапшот — правка после прогона; translate потребует --resnapshot = переоплата книги)"
|
||||
}
|
||||
fmt.Fprintf(w, "=== СТАТУС: %s (snapshot %s)%s ===\n", rep.BookID, snap, drift)
|
||||
fmt.Fprintf(w, "Прогресс: %d/%d чанков (%.1f%%) — done=%d in_progress=%d flagged=%d pending=%d\n",
|
||||
rep.Done, rep.TotalChunks, rep.PercentDone, rep.Done, rep.InProgress, rep.Flagged, rep.Pending)
|
||||
fmt.Fprintf(w, "Прогресс: %d/%d единиц (%.1f%%) — done=%d in_progress=%d flagged=%d pending=%d\n",
|
||||
rep.Done, rep.TotalUnits, rep.PercentDone, rep.Done, rep.InProgress, rep.Flagged, rep.Pending)
|
||||
fmt.Fprintf(w, "Эскалаций: %d · post-check промахов (confirmed): %d · стиль-флагов (наблюдаемость): %d\n",
|
||||
rep.Escalations, rep.PostcheckMisses, rep.StyleFlags)
|
||||
ceil := ""
|
||||
|
|
@ -335,7 +350,7 @@ func renderStatusHuman(w io.Writer, rep *pipeline.StatusReport, cfgPath string)
|
|||
fmt.Fprintf(w, "\n%-5s %-9s %-10s %-6s %-4s %-6s %-16s %10s\n", "ch", "done/all", "verdict", "flag", "esc", "style", "worst_flag", "cost_usd")
|
||||
for _, p := range rep.Chapters {
|
||||
fmt.Fprintf(w, "%-5d %d/%-7d %-10s %-6d %-4d %-6d %-16s %10.6f\n",
|
||||
p.Chapter, p.ChunksDone, p.ChunksTotal, p.Verdict, p.ChunksFlagged, p.Escalations, p.StyleFlags,
|
||||
p.Chapter, p.UnitsDone, p.UnitsTotal, p.Verdict, p.UnitsFlagged, p.Escalations, p.StyleFlags,
|
||||
dashIfEmpty(p.WorstFlagReason), p.CostUSD)
|
||||
}
|
||||
}
|
||||
|
|
@ -353,7 +368,7 @@ func renderStatusHuman(w io.Writer, rep *pipeline.StatusReport, cfgPath string)
|
|||
fmt.Fprintf(w, "\n%d чанк(ов) флагнуты post-check-гейтом (glossary_miss) — redrive для них no-op: исправьте сид-глоссарий (термин/dst) и перезапустите `tmctl translate --config %s --resnapshot` (переоплата затронутых чанков)\n",
|
||||
rep.GlossaryMissFlagged, cfgPath)
|
||||
}
|
||||
return &pipeline.CompletedWithFlags{Flagged: rep.Flagged, Total: rep.TotalChunks}
|
||||
return &pipeline.CompletedWithFlags{Flagged: rep.Flagged, Total: rep.TotalUnits}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ func TestRenderReportPartialOutputOnMidAuditError(t *testing.T) {
|
|||
|
||||
func TestRenderStatusJSONSchemaAndSentinel(t *testing.T) {
|
||||
var b strings.Builder
|
||||
rep := &pipeline.StatusReport{BookID: "b1", TotalChunks: 4, Done: 3, Flagged: 1}
|
||||
rep := &pipeline.StatusReport{BookID: "b1", TotalUnits: 4, Done: 3, Flagged: 1}
|
||||
err := renderStatusJSON(&b, rep)
|
||||
var flagged *pipeline.CompletedWithFlags
|
||||
if !errors.As(err, &flagged) {
|
||||
|
|
@ -188,7 +188,7 @@ func TestRenderStatusJSONSchemaAndSentinel(t *testing.T) {
|
|||
t.Fatalf("stable schema field book_id missing: %v", decoded)
|
||||
}
|
||||
// Clean book → nil error → exit 0.
|
||||
if err := renderStatusJSON(&strings.Builder{}, &pipeline.StatusReport{TotalChunks: 4, Done: 4}); err != nil {
|
||||
if err := renderStatusJSON(&strings.Builder{}, &pipeline.StatusReport{TotalUnits: 4, Done: 4}); err != nil {
|
||||
t.Fatalf("clean book must exit 0, got %v", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -196,7 +196,7 @@ func TestRenderStatusJSONSchemaAndSentinel(t *testing.T) {
|
|||
func TestRenderStatusHumanDriftAndAdvice(t *testing.T) {
|
||||
var b strings.Builder
|
||||
rep := &pipeline.StatusReport{
|
||||
BookID: "b1", Snapshot: "abcdef0123456789", TotalChunks: 10, Done: 7,
|
||||
BookID: "b1", Snapshot: "abcdef0123456789", TotalUnits: 10, Done: 7,
|
||||
Flagged: 3, GlossaryMissFlagged: 1, SnapshotDrift: true, ConfigDrift: true,
|
||||
}
|
||||
err := renderStatusHuman(&b, rep, "book.yaml")
|
||||
|
|
|
|||
|
|
@ -69,8 +69,14 @@ type Book struct {
|
|||
// R1). Its terms are loaded as Source:"mined" (NOT Source:"seed"), so they fold into the ENRICHED bank
|
||||
// version but NOT the base — adding them moves only snapshot_W2 («переоплата ОДНА»). Same seedTerm
|
||||
// schema as glossary_seed; resolved relative to book.yaml. Empty = no mined terms.
|
||||
MinedDelta string `yaml:"mined_delta"`
|
||||
Ceilings BookCap `yaml:"ceilings"`
|
||||
MinedDelta string `yaml:"mined_delta"`
|
||||
// MinedRejects is the optional path to the owner's mined-term REJECT list YAML (R1-FL-B). It lists the
|
||||
// src surfaces the owner reviewed and DECLINED, so the bank-mining stop stops re-proposing them every
|
||||
// run (else a declined term re-fires the stop forever — a livelock). Unlike MinedDelta it is a PROPOSAL
|
||||
// filter only: rejects NEVER enter the bank content, so they are deliberately NOT folded into the
|
||||
// snapshot. Resolved relative to book.yaml. Empty = no rejects.
|
||||
MinedRejects string `yaml:"mined_rejects"`
|
||||
Ceilings BookCap `yaml:"ceilings"`
|
||||
}
|
||||
|
||||
// BookCap are the ledger admission limits (Р7: потолок $ на книгу/день).
|
||||
|
|
@ -103,6 +109,7 @@ func LoadBook(path string) (*Book, error) {
|
|||
b.GlossarySeed = resolve(b.GlossarySeed)
|
||||
b.LangpackRoot = resolve(b.LangpackRoot)
|
||||
b.MinedDelta = resolve(b.MinedDelta)
|
||||
b.MinedRejects = resolve(b.MinedRejects)
|
||||
if b.ProjectDB == "" {
|
||||
b.ProjectDB = filepath.Join(dir, b.BookID+".db")
|
||||
} else {
|
||||
|
|
@ -141,6 +148,11 @@ func LoadBook(path string) (*Book, error) {
|
|||
bad("mined_delta %s is not readable: %v", b.MinedDelta, err)
|
||||
}
|
||||
}
|
||||
if b.MinedRejects != "" {
|
||||
if _, err := os.Stat(b.MinedRejects); err != nil {
|
||||
bad("mined_rejects %s is not readable: %v", b.MinedRejects, err)
|
||||
}
|
||||
}
|
||||
if b.Encoding == "" {
|
||||
b.Encoding = "auto"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,11 +48,11 @@ type ChunkExport struct {
|
|||
// instead of silently exporting as complete (F4/F3).
|
||||
type BookExport struct {
|
||||
BookID string `json:"book_id"`
|
||||
// TotalChunks is the current source manifest size (the honest denominator); ExportedChunks +
|
||||
// PendingChunks == TotalChunks. GhostRows are stored final rows dropped as OUTSIDE the manifest.
|
||||
TotalChunks int `json:"total_chunks"`
|
||||
PendingChunks int `json:"pending_chunks"`
|
||||
GhostRows int `json:"ghost_rows,omitempty"`
|
||||
// TotalUnits is the current source manifest size in output units (the honest denominator); the exported
|
||||
// units + PendingUnits == TotalUnits. GhostRows are stored final rows dropped as OUTSIDE the manifest.
|
||||
TotalUnits int `json:"total_units"`
|
||||
PendingUnits int `json:"pending_units"`
|
||||
GhostRows int `json:"ghost_rows,omitempty"`
|
||||
// ConfigDrift is true when the CURRENT config renders a snapshot different from the one the stored
|
||||
// rows carry (a gate flip / prompt bump / stage rename since the run) — the gate/stage re-derivation
|
||||
// below may then not match what translate actually did. CurrentSnapshot is that projected id.
|
||||
|
|
@ -143,14 +143,14 @@ func (r *Runner) Export(pairs bool) (*BookExport, error) {
|
|||
for _, u := range units {
|
||||
key := chunkKey{u.Chapter, u.FirstChunkIdx}
|
||||
inManifest[key] = true
|
||||
exp.TotalChunks++
|
||||
exp.TotalUnits++
|
||||
cs, ok := finalRow[key]
|
||||
if !ok {
|
||||
pend := ChunkExport{Chapter: u.Chapter, ChunkIdx: u.FirstChunkIdx, Disposition: exportPending}
|
||||
if pairs {
|
||||
pend.Source = u.sourceText()
|
||||
}
|
||||
exp.PendingChunks++
|
||||
exp.PendingUnits++
|
||||
exp.Chunks = append(exp.Chunks, pend)
|
||||
continue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -201,14 +201,14 @@ func TestExportManifestPending(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("Export: %v", err)
|
||||
}
|
||||
if exp.TotalChunks != 3 {
|
||||
t.Fatalf("manifest must be 3 chunks, got total=%d", exp.TotalChunks)
|
||||
if exp.TotalUnits != 3 {
|
||||
t.Fatalf("manifest must be 3 chunks, got total=%d", exp.TotalUnits)
|
||||
}
|
||||
if exp.PendingChunks < 1 {
|
||||
t.Fatalf("a ceiling-stopped book must have pending chunks, got pending=%d (%+v)", exp.PendingChunks, exp.Chunks)
|
||||
if exp.PendingUnits < 1 {
|
||||
t.Fatalf("a ceiling-stopped book must have pending chunks, got pending=%d (%+v)", exp.PendingUnits, exp.Chunks)
|
||||
}
|
||||
if len(exp.Chunks) != exp.TotalChunks {
|
||||
t.Fatalf("every manifest chunk must have a row: len=%d total=%d", len(exp.Chunks), exp.TotalChunks)
|
||||
if len(exp.Chunks) != exp.TotalUnits {
|
||||
t.Fatalf("every manifest chunk must have a row: len=%d total=%d", len(exp.Chunks), exp.TotalUnits)
|
||||
}
|
||||
var pending int
|
||||
for _, ce := range exp.Chunks {
|
||||
|
|
@ -219,8 +219,8 @@ func TestExportManifestPending(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
if pending != exp.PendingChunks {
|
||||
t.Errorf("pending rows (%d) must match the counter (%d)", pending, exp.PendingChunks)
|
||||
if pending != exp.PendingUnits {
|
||||
t.Errorf("pending rows (%d) must match the counter (%d)", pending, exp.PendingUnits)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,9 +45,16 @@ var trad2simpRaw string
|
|||
// unicode.Han/Cyrillic predicates) changes this version → the snapshot moves → a LOUD --resnapshot,
|
||||
// never a silent match-behaviour change on already-checkpointed chunks. TRIPWIRE: do NOT bump the
|
||||
// toolchain past the pinned Unicode edition without a --resnapshot (we are on go1.26.4 / Unicode 15.0.0).
|
||||
// (x/text/norm carries its OWN Unicode edition independent of stdlib unicode.Version — a standalone x/text
|
||||
// dependency bump must be treated like a toolchain bump, i.e. a deliberate --resnapshot; residual noted.)
|
||||
const memoryNormAlgoVersion = "memnorm-v3-nfkc+apos+stripignorable+trad+kana+lower/nfc+dash+apos+stripignorable+lower+yofold+u" + unicode.Version
|
||||
//
|
||||
// R1-FL-C: golang.org/x/text/norm carries its OWN Unicode edition INDEPENDENT of stdlib unicode.Version
|
||||
// (its NFKC/NFC tables ship in the module, not the toolchain), so a standalone `go get -u golang.org/x/text`
|
||||
// could silently shift normalizeSourceKey's NFKC output → a re-verdict on the post-check WITHOUT moving the
|
||||
// snapshot. Weaving xTextVersion in makes an x/text bump a loud --resnapshot too. It is a PINNED constant
|
||||
// (debug.ReadBuildInfo returns an empty x/text version under `go test`, where the golden is captured, so a
|
||||
// build-info fold would be non-deterministic) DRIFT-CHECKED against go.mod by TestMemnormXTextVersionPin:
|
||||
// a go.mod bump that forgets this constant fails that test loudly — the tripwire enforced by mechanism.
|
||||
const xTextVersion = "v0.38.0" // MUST equal go.mod's golang.org/x/text (asserted by TestMemnormXTextVersionPin); a bump = --resnapshot
|
||||
const memoryNormAlgoVersion = "memnorm-v3-nfkc+apos+stripignorable+trad+kana+lower/nfc+dash+apos+stripignorable+lower+yofold+u" + unicode.Version + "+xtext" + xTextVersion
|
||||
|
||||
var (
|
||||
// trad2simp is the parsed Traditional→Simplified single-char map (data/trad2simp.txt).
|
||||
|
|
|
|||
|
|
@ -1,12 +1,47 @@
|
|||
package pipeline
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"textmachine/backend/internal/store"
|
||||
)
|
||||
|
||||
// TestMemnormXTextVersionPin is the R1-FL-C drift tripwire: the xTextVersion constant folded into
|
||||
// memoryNormAlgoVersion MUST equal the golang.org/x/text version go.mod pins. x/text/norm ships its own
|
||||
// Unicode edition, so a `go get -u golang.org/x/text` that forgets to bump the constant would silently
|
||||
// shift NFKC normalization WITHOUT moving the snapshot — this test turns that into a loud failure
|
||||
// (the bump is then a deliberate --resnapshot, re-capturing the golden). Reads the module-root go.mod.
|
||||
func TestMemnormXTextVersionPin(t *testing.T) {
|
||||
f, err := os.Open("../../go.mod")
|
||||
if err != nil {
|
||||
t.Fatalf("open go.mod: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
want := ""
|
||||
sc := bufio.NewScanner(f)
|
||||
for sc.Scan() {
|
||||
fields := strings.Fields(sc.Text())
|
||||
// A require line: "golang.org/x/text v0.38.0" (possibly with a trailing "// indirect").
|
||||
if len(fields) >= 2 && fields[0] == "golang.org/x/text" {
|
||||
want = fields[1]
|
||||
break
|
||||
}
|
||||
}
|
||||
if err := sc.Err(); err != nil {
|
||||
t.Fatalf("scan go.mod: %v", err)
|
||||
}
|
||||
if want == "" {
|
||||
t.Fatal("golang.org/x/text not found in go.mod")
|
||||
}
|
||||
if xTextVersion != want {
|
||||
t.Fatalf("xTextVersion = %q but go.mod pins golang.org/x/text %q — bump the constant IN LOCKSTEP "+
|
||||
"(x/text carries its own Unicode edition; a bump is a deliberate --resnapshot, re-capture the golden)", xTextVersion, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTradTableCoversKnownCases asserts the embedded trad→simp table covers every
|
||||
// character referenced by the eval specs / research/14 live bugs. A mutation
|
||||
// (deleting a mapping line) fails exactly here — the guard against silently
|
||||
|
|
|
|||
|
|
@ -518,41 +518,6 @@ func SeedLint(path string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// --- mined → candidates (WS3 bank-mining mined-write path) -----------------------------
|
||||
|
||||
// minedToCandidates converts the miner's WHICH proposals (MineBank) into store candidates for the the bank-mining stop
|
||||
// reseed (WS3 / §1в mined-write path). It is the DEDICATED mined path the plan requires: it stamps
|
||||
// Source:"mined" (mirroring rubyToCandidates' Source:"ruby"), NOT the Source:"seed" that loadGlossarySeed
|
||||
// hardcodes — so a mined row is EXCLUDED from the base-bank-version (BaseVersion) and the the bank-mining stop bank
|
||||
// enrichment moves ONLY snapshot_W2, keeping the draft wave checkpoints valid («переоплата ОДНА», §1в/F2). Every
|
||||
// candidate is status=auto with NO dst (the WHAT is delivered by the banknote/owner sign, WS4): inert on
|
||||
// the hot path until a human/batch promotes it, never a blind name-lock. A src already covered by the
|
||||
// manual seed is skipped (the curated entry wins), exactly like rubyToCandidates. Deterministic (MineBank
|
||||
// emits a src-sorted delta; aliases are re-sorted on read by GlossaryForBook).
|
||||
func minedToCandidates(mined []MinedTerm, manualSrcs map[string]bool) []store.GlossaryEntry {
|
||||
var out []store.GlossaryEntry
|
||||
for _, m := range mined {
|
||||
if manualSrcs[m.Src] {
|
||||
continue // the curated seed already owns this src
|
||||
}
|
||||
var aliases []store.GlossaryAlias
|
||||
seen := map[string]bool{}
|
||||
for _, a := range m.Aliases {
|
||||
if a == "" || a == m.Src || seen[a] {
|
||||
continue
|
||||
}
|
||||
seen[a] = true
|
||||
aliases = append(aliases, store.GlossaryAlias{Alias: a, AliasType: "mined"})
|
||||
}
|
||||
out = append(out, store.GlossaryEntry{
|
||||
Src: m.Src, Dst: "", Type: m.Type, Status: "auto", Source: "mined",
|
||||
SinceCh: m.SinceCh, Confidence: m.Freq, Aliases: aliases,
|
||||
Note: "mined WHICH candidate (miner-v1); dst delivered by banknote/owner sign — promote before use",
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ruby classifier classes (deterministic HINTS for human promotion).
|
||||
const (
|
||||
rubyClassName = "name_candidate" // all-Han base + all-kana reading: the furigana-name SHAPE
|
||||
|
|
|
|||
|
|
@ -43,8 +43,11 @@ type MinedTerm struct {
|
|||
|
||||
// MineBank runs the full default-B miner over the normalized chunks and emits the alias-clustered,
|
||||
// filtered mined delta (WHICH candidates for owner sign). seed is the current glossary (its surfaces
|
||||
// scope the non-seed filter, the fragment guard, and the alias entity/metadata). Deterministic and $0.
|
||||
func MineBank(chunks []MinerChunk, contrast *Contrast, seed []store.GlossaryEntry, cfg MinerConfig, pack *lang.Pack) []MinedTerm {
|
||||
// scope the non-seed filter, the fragment guard, and the alias entity/metadata). rejects is the owner's
|
||||
// declined-term set (normalized src, R1-FL-B): a candidate whose entity touches a rejected surface is
|
||||
// dropped from the delta so a declined term never re-fires the stop (a nil/empty rejects is a no-op — the
|
||||
// emission is byte-identical to before, preserving the frozen parity). Deterministic and $0.
|
||||
func MineBank(chunks []MinerChunk, contrast *Contrast, seed []store.GlossaryEntry, rejects map[string]bool, cfg MinerConfig, pack *lang.Pack) []MinedTerm {
|
||||
mr := mineDetect(chunks, contrast, cfg, pack)
|
||||
|
||||
// Seed surfaces (normalized) + their metadata for the alias rules and the non-seed / fragment guards.
|
||||
|
|
@ -125,7 +128,16 @@ func MineBank(chunks []MinerChunk, contrast *Contrast, seed []store.GlossaryEntr
|
|||
}
|
||||
cl := clusterOf[c.Src]
|
||||
// A cluster touching a seed surface is an alias-of-existing entity → the owner attaches it; skip.
|
||||
if clusterHasSeed(cl, seedSurfaces) {
|
||||
if clusterTouches(cl, seedSurfaces) {
|
||||
markEmitted(cl, emitted)
|
||||
continue
|
||||
}
|
||||
// A declined entity (R1-FL-B) is dropped WITH its whole cluster, so it never re-fires the stop:
|
||||
// marking the cluster emitted stops a lower-ranked member from re-emitting the same entity next run
|
||||
// (which would keep the delta non-empty forever). rejects[c.Src] covers a SINGLETON representative
|
||||
// (cl is nil for a candidate in no multi-cluster); clusterTouches covers a rejected alias of a
|
||||
// multi-member cluster. markEmitted(nil,...) is a harmless no-op for the singleton case.
|
||||
if rejects[c.Src] || clusterTouches(cl, rejects) {
|
||||
markEmitted(cl, emitted)
|
||||
continue
|
||||
}
|
||||
|
|
@ -205,9 +217,12 @@ func hasAnyType(types []string, want ...string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func clusterHasSeed(cl []string, seedSurfaces map[string]bool) bool {
|
||||
// clusterTouches reports whether any surface of the identity cluster is in `set` — used both for the seed
|
||||
// surfaces (an alias-of-existing entity) and the reject surfaces (a declined entity), which are treated the
|
||||
// same way at emission: the whole cluster is suppressed.
|
||||
func clusterTouches(cl []string, set map[string]bool) bool {
|
||||
for _, s := range cl {
|
||||
if seedSurfaces[s] {
|
||||
if set[s] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -285,8 +285,8 @@ func TestMineBankDeterministicAndNonSeed(t *testing.T) {
|
|||
chunks := mchunks(strings.Repeat("龙公来到青茅山。龙公很强。青茅山很高。", 4))
|
||||
seed := []store.GlossaryEntry{{Src: "青茅山", Dst: "гора Цинмао", Type: "place", Status: "approved", Source: "seed"}}
|
||||
cfg := frozenMinerConfig()
|
||||
a := MineBank(chunks, smallContrast(t), seed, cfg, testLangPack(t))
|
||||
b := MineBank(chunks, smallContrast(t), seed, cfg, testLangPack(t))
|
||||
a := MineBank(chunks, smallContrast(t), seed, nil, cfg, testLangPack(t))
|
||||
b := MineBank(chunks, smallContrast(t), seed, nil, cfg, testLangPack(t))
|
||||
if !reflect.DeepEqual(a, b) {
|
||||
t.Fatalf("MineBank must be deterministic byte-for-byte:\n a=%+v\n b=%+v", a, b)
|
||||
}
|
||||
|
|
@ -298,43 +298,62 @@ func TestMineBankDeterministicAndNonSeed(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestMinedToCandidatesStampsMinedSource(t *testing.T) {
|
||||
mined := []MinedTerm{
|
||||
{Src: "龙公", Type: "name", SinceCh: 3, Freq: 7, Aliases: []string{"龙公子"}},
|
||||
{Src: "青茅山", Type: "place", SinceCh: 1, Freq: 9}, // covered by manual seed → skipped
|
||||
// TestMineBankRejectSuppressesTerm pins R1-FL-B: a term the owner declined (mined_rejects) is dropped from
|
||||
// the delta, so it does NOT re-fire the stop — while the OTHER proposals (which the owner has not decided)
|
||||
// still fire. The stop clears only once every proposal is promoted (into the seed) or rejected.
|
||||
func TestMineBankRejectSuppressesTerm(t *testing.T) {
|
||||
chunks := mchunks(strings.Repeat("方源来到青茅山。方源很强。花家很大。花家的人。", 6))
|
||||
seed := []store.GlossaryEntry{{Src: "青茅山", Dst: "гора Цинмао", Type: "place", Status: "approved", Source: "seed"}}
|
||||
cfg := frozenMinerConfig()
|
||||
fangYuan, huaJia := normalizeSourceKey("方源"), normalizeSourceKey("花家")
|
||||
|
||||
base := MineBank(chunks, smallContrast(t), seed, nil, cfg, testLangPack(t))
|
||||
if !mineHas(base, fangYuan) || !mineHas(base, huaJia) {
|
||||
t.Fatalf("fixture must propose both 方源 and 花家 for the reject test to be meaningful, got %+v", base)
|
||||
}
|
||||
manual := map[string]bool{"青茅山": true}
|
||||
got := minedToCandidates(mined, manual)
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("want 1 mined candidate (青茅山 skipped as manual), got %d", len(got))
|
||||
|
||||
// Reject 方源 (normalized as loadMinedRejects would): it must vanish, but 花家 (still undecided) fires.
|
||||
got := MineBank(chunks, smallContrast(t), seed, map[string]bool{fangYuan: true}, cfg, testLangPack(t))
|
||||
if mineHas(got, fangYuan) {
|
||||
t.Fatalf("a rejected term was re-emitted → the stop would livelock: %+v", got)
|
||||
}
|
||||
e := got[0]
|
||||
if e.Source != "mined" {
|
||||
t.Fatalf("mined candidate Source = %q, want \"mined\" (NOT seed — else it moves base-bank-version)", e.Source)
|
||||
if !mineHas(got, huaJia) {
|
||||
t.Fatalf("rejecting one term must not drop an unrelated undecided term (花家): %+v", got)
|
||||
}
|
||||
if e.Status != "auto" || e.Dst != "" {
|
||||
t.Fatalf("mined candidate must be status=auto with no dst, got status=%q dst=%q", e.Status, e.Dst)
|
||||
|
||||
// Reject BOTH → the delta empties → the stop auto-continues (every proposal is now decided).
|
||||
both := MineBank(chunks, smallContrast(t), seed, map[string]bool{fangYuan: true, huaJia: true}, cfg, testLangPack(t))
|
||||
if len(both) != 0 {
|
||||
t.Fatalf("rejecting every proposal must empty the delta (auto-continue), got %d: %+v", len(both), both)
|
||||
}
|
||||
if e.SinceCh != 3 || e.Confidence != 7 {
|
||||
t.Fatalf("mined candidate metadata wrong: %+v", e)
|
||||
|
||||
// A nil / empty rejects map must be a no-op (byte-identical to base), so an unconfigured book — and the
|
||||
// frozen parity — are unaffected.
|
||||
if noop := MineBank(chunks, smallContrast(t), seed, map[string]bool{}, cfg, testLangPack(t)); !reflect.DeepEqual(noop, base) {
|
||||
t.Fatalf("an empty rejects map must be a no-op:\n base=%+v\n got=%+v", base, noop)
|
||||
}
|
||||
if len(e.Aliases) != 1 || e.Aliases[0].Alias != "龙公子" || e.Aliases[0].AliasType != "mined" {
|
||||
t.Fatalf("mined alias wrong: %+v", e.Aliases)
|
||||
}
|
||||
|
||||
// mineHas reports whether the delta contains a term with the given normalized src.
|
||||
func mineHas(delta []MinedTerm, src string) bool {
|
||||
for _, m := range delta {
|
||||
if m.Src == src {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TestMinedDeltaStaysBaseBankStable(t *testing.T) {
|
||||
// The mined delta (Source:"mined") must NOT move BaseVersion — the load-bearing «переоплата ОДНА»
|
||||
// invariant (§1в): a the draft wave.5 mined-row addition moves only the enriched version. (Sibling of the
|
||||
// Block-A TestBaseEnrichedMemoryVersionSplit, here through the real mined-write path.)
|
||||
// invariant (§1в): a bank-mining mined-row addition moves only the enriched version. The live mined-write
|
||||
// path (loadMinedDelta, mining.go) stamps Source:"mined"; this pins the invariant that stamp guarantees.
|
||||
// (Sibling of the Block-A TestBaseEnrichedMemoryVersionSplit.)
|
||||
seed := []store.GlossaryEntry{{Src: "方源", Dst: "Фан Юань", Status: "approved", Source: "seed"}}
|
||||
base0 := materializeMemory(seed, false)
|
||||
mined := minedToCandidates([]MinedTerm{{Src: "龙公", Type: "name", SinceCh: 1, Freq: 5}}, map[string]bool{"方源": true})
|
||||
// A mined candidate is status=auto (not folded even into enriched when gate off) — promote it to
|
||||
// approved to exercise the split, as the owner sign would at the draft wave.5.
|
||||
mined[0].Status = "approved"
|
||||
mined[0].Dst = "Лун Гун"
|
||||
enriched := append(append([]store.GlossaryEntry{}, seed...), mined...)
|
||||
// An owner-promoted mined term (approved + dst, Source:"mined"), as the sign step emits it.
|
||||
mined := store.GlossaryEntry{Src: "龙公", Dst: "Лун Гун", Type: "name", Status: "approved", Source: "mined", SinceCh: 1, Confidence: 5}
|
||||
enriched := append(append([]store.GlossaryEntry{}, seed...), mined)
|
||||
bank1 := materializeMemory(enriched, false)
|
||||
if base0.BaseVersion() != bank1.BaseVersion() {
|
||||
t.Fatalf("base-bank-version moved on a Source:mined addition — draft-wave snapshot would re-bill the draft wave")
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"textmachine/backend/internal/store"
|
||||
)
|
||||
|
||||
|
|
@ -51,7 +53,15 @@ func (r *Runner) runBankMiningStop(ctx context.Context, chunks []Chunk) (stopped
|
|||
if err != nil {
|
||||
return false, fmt.Errorf("pipeline: the bank-mining stop read glossary for mining: %w", err)
|
||||
}
|
||||
mined := MineBank(minerChunks, contrast, seed, frozenMinerConfig(), r.pack)
|
||||
// The owner's reject list excludes declined terms from the emission (R1-FL-B): a term the owner reviewed
|
||||
// and rejected is dropped from the delta exactly like a seed surface, so it never re-fires the stop. The
|
||||
// stop therefore clears once EVERY proposed term is EITHER promoted (into mined_delta → a seed surface)
|
||||
// OR rejected (mined_rejects) — the two owner verbs that empty the delta.
|
||||
rejects, err := r.loadMinedRejects()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
mined := MineBank(minerChunks, contrast, seed, rejects, frozenMinerConfig(), r.pack)
|
||||
r.lastMinedCount = len(mined)
|
||||
if len(mined) == 0 {
|
||||
r.Log.InfoContext(ctx, "bank-mining: empty delta, auto-continuing to the edit wave", "book", r.Book.BookID)
|
||||
|
|
@ -66,11 +76,51 @@ func (r *Runner) runBankMiningStop(ctx context.Context, chunks []Chunk) (stopped
|
|||
if err := os.WriteFile(r.signatureMapPath(), []byte(yamlDelta), 0o644); err != nil {
|
||||
return false, fmt.Errorf("pipeline: the bank-mining stop write signature map %s: %w", r.signatureMapPath(), err)
|
||||
}
|
||||
r.Log.WarnContext(ctx, "bank-mining: new terms await owner signature; run STOPPED before the edit wave (review the signature map, promote terms into the mined-delta file, then resume)",
|
||||
r.Log.WarnContext(ctx, "bank-mining: new terms await owner signature; run STOPPED before the edit wave (review the signature map, then for EACH term either promote it into the mined-delta file OR decline it in the mined-rejects file, then resume — the stop clears once every proposed term is promoted or rejected)",
|
||||
"book", r.Book.BookID, "terms", len(mined), "signature_map", r.signatureMapPath())
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// minedRejectFile is the owner's mined-term reject list (Book.MinedRejects, R1-FL-B): the src surfaces the
|
||||
// owner reviewed and DECLINED. It is a PROPOSAL filter only — rejects never enter the bank content, so this
|
||||
// file is deliberately NOT folded into the snapshot (a reject affects the next mining PROPOSAL, not any
|
||||
// checkpoint's wire/verdict).
|
||||
type minedRejectFile struct {
|
||||
Rejects []minedReject `yaml:"rejects"`
|
||||
}
|
||||
|
||||
// minedReject is one declined mined term. Note is the owner's optional reason, ignored by the miner but
|
||||
// kept so a reject list stays self-documenting (six months on, "why was this declined").
|
||||
type minedReject struct {
|
||||
Src string `yaml:"src"`
|
||||
Note string `yaml:"note,omitempty"`
|
||||
}
|
||||
|
||||
// loadMinedRejects reads Book.MinedRejects and returns the normalized src set the emission excludes (like
|
||||
// the seed surfaces). Empty path → nil (no rejects). Each src is normalized via normalizeSourceKey so a
|
||||
// reject matches the miner's normalized candidate surface whichever orthographic form the owner pasted from
|
||||
// the signature map; a blank src is skipped (a stray list entry must not silently match everything).
|
||||
func (r *Runner) loadMinedRejects() (map[string]bool, error) {
|
||||
if r.Book.MinedRejects == "" {
|
||||
return nil, nil
|
||||
}
|
||||
raw, err := os.ReadFile(r.Book.MinedRejects)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("pipeline: read mined-rejects %s: %w", r.Book.MinedRejects, err)
|
||||
}
|
||||
var f minedRejectFile
|
||||
if err := yaml.Unmarshal(raw, &f); err != nil {
|
||||
return nil, fmt.Errorf("pipeline: parse mined-rejects %s: %w", r.Book.MinedRejects, err)
|
||||
}
|
||||
rejects := map[string]bool{}
|
||||
for _, rj := range f.Rejects {
|
||||
if nk := normalizeSourceKey(rj.Src); nk != "" {
|
||||
rejects[nk] = true
|
||||
}
|
||||
}
|
||||
return rejects, nil
|
||||
}
|
||||
|
||||
// loadMinedDelta reads the owner-curated mined-delta YAML (book.MinedDelta) and stamps every entry
|
||||
// Source:"mined" — NOT via loadGlossarySeed (which hardcodes Source:"seed", memseed.go, moving the base
|
||||
// bank / draft-wave snapshot). This is the mined-write path (plan §1(в), F2): the mined terms land in the ENRICHED
|
||||
|
|
|
|||
|
|
@ -15,18 +15,19 @@ import (
|
|||
// no LLM, no snapshot touch, no checkpoint replay — only OBSERVABILITY, never a gate. The semantic
|
||||
// span-judge (inversion/omission backstop for claim-2) is NOT here — it is research-dependent (пак-2).
|
||||
|
||||
// QualityReport is the whole-book per-run quality projection.
|
||||
// QualityReport is the whole-book per-run quality projection. The shipping granularity is the OUTPUT UNIT
|
||||
// (edit unit for an edit pipeline, draft chunk for a draft-only one), so the whole-book counters are named
|
||||
// *_units (naming-debt fix D39.18-follow-up: pre-c-lite they said "chunks" but count units).
|
||||
type QualityReport struct {
|
||||
BookID string `json:"book_id"`
|
||||
TotalChunks int `json:"total_chunks"`
|
||||
// TextChunks is the number of chunks whose exported final text was available for the structural
|
||||
// KPI (done or cosmetically-stripped); a flagged-empty chunk contributes no prose.
|
||||
TextChunks int `json:"text_chunks"`
|
||||
// ProcessedChunks is the number of chunks that REACHED the final stage (a final-stage row exists:
|
||||
// ok, cosmetic-strip, or skipped-because-upstream-flagged) — the rate denominator, so echo/CJK
|
||||
// rates are a bounded [0,1] fraction of processed chunks (an echo chunk is a skipped final row,
|
||||
// disjoint from the exported-text set, which is why dividing by TextChunks alone could exceed 1).
|
||||
ProcessedChunks int `json:"processed_chunks"`
|
||||
BookID string `json:"book_id"`
|
||||
TotalUnits int `json:"total_units"`
|
||||
// TextUnits is the number of units whose exported final text was available for the structural KPI
|
||||
// (done or cosmetically-stripped); a flagged-empty unit contributes no prose.
|
||||
TextUnits int `json:"text_units"`
|
||||
// ProcessedUnits is the number of units that REACHED the final stage (a final-stage row exists: ok,
|
||||
// cosmetic-strip, or skipped-because-a-member-flagged) — the strip-rate denominator, so the rate is a
|
||||
// bounded [0,1] fraction of processed units.
|
||||
ProcessedUnits int `json:"processed_units"`
|
||||
|
||||
// Claim-1 structural KPI (рубленые абзацы). MeanSentPerNarrPara ≈ 1 is choppy (one sentence per
|
||||
// paragraph — the owner's exact complaint); higher is merged discourse prose. Aggregated as
|
||||
|
|
@ -36,19 +37,27 @@ type QualityReport struct {
|
|||
MeanSentPerNarrPara float64 `json:"mean_sentences_per_narrative_paragraph"`
|
||||
|
||||
// Deterministic signal aggregates (all observability, never a disposition).
|
||||
DialogueDashFlags int `json:"dialogue_dash_flags"` // Rosenthal dialogue-dash inconsistencies
|
||||
GlossaryMisses int `json:"glossary_misses"` // CONFIRMED post-check misses (D10 consistency)
|
||||
NumberDriftFlags int `json:"number_drift_flags"` // reflow number drift + 万/億 magnitude drift
|
||||
TrustGated int `json:"trust_gated"` // lower-trust suppressions refused (seed hygiene)
|
||||
CosmeticStripChunks int `json:"cosmetic_strip_chunks"` // chunks the sanitizer auto-stripped (markdown header OR CJK leak — F6: was mislabeled cjk_leak)
|
||||
EchoChunks int `json:"echo_chunks"` // chunks flagged cjk_artifact (untranslated echo)
|
||||
DialogueDashFlags int `json:"dialogue_dash_flags"` // Rosenthal dialogue-dash inconsistencies
|
||||
GlossaryMisses int `json:"glossary_misses"` // CONFIRMED post-check misses (D10 consistency)
|
||||
NumberDriftFlags int `json:"number_drift_flags"` // reflow number drift + 万/億 magnitude drift
|
||||
TrustGated int `json:"trust_gated"` // lower-trust suppressions refused (seed hygiene)
|
||||
CosmeticStripUnits int `json:"cosmetic_strip_units"` // units the sanitizer auto-stripped (markdown header OR CJK leak — F6)
|
||||
|
||||
// Rates over ProcessedChunks (0..1) for the two "instant unreadability" families, so the numbers
|
||||
// read as a bounded fraction of processed chunks rather than a bare count. CosmeticStripRate covers
|
||||
// BOTH strip classes (a markdown-only strip is NOT a CJK leak — F6, D39.4: the old cjk_leak_rate
|
||||
// counted every sanitizer_stripped chunk and read as false CJK leakage).
|
||||
// Echo is SPLIT by stage (owner decision, D39.18-follow-up): a translator echo (draft) and an editor
|
||||
// echo (edit) measure different things and were conflated by the old single echo_rate + a c-lite
|
||||
// re-derive hack. echo_draft = the DRAFT quality (fraction of draft-stage rows flagged cjk_artifact,
|
||||
// INCLUDING a c-lite dropped member — the translator echoed even if the editor recovered the unit),
|
||||
// computed DIRECTLY from the draft rows (no per-unit re-derivation). echo_edit = the DELIVERED quality
|
||||
// (fraction of edit-stage units whose EDITOR output itself echoed — a skipped edit row is a draft echo,
|
||||
// not an editor one, so it is excluded from the numerator).
|
||||
EchoDraftChunks int `json:"echo_draft_chunks"` // draft-stage rows flagged cjk_artifact (per DRAFT chunk)
|
||||
EchoDraftRate float64 `json:"echo_draft_rate"` // over live draft rows
|
||||
EchoEditUnits int `json:"echo_edit_units"` // edit-stage units whose editor output echoed
|
||||
EchoEditRate float64 `json:"echo_edit_rate"` // over live edit rows
|
||||
|
||||
// CosmeticStripRate is over ProcessedUnits (0..1). It covers BOTH strip classes (a markdown-only strip
|
||||
// is NOT a CJK leak — F6, D39.4: the old cjk_leak_rate counted every sanitizer_stripped unit).
|
||||
CosmeticStripRate float64 `json:"cosmetic_strip_rate"`
|
||||
EchoRate float64 `json:"echo_rate"`
|
||||
|
||||
Chunks []ChunkQuality `json:"chunks,omitempty"`
|
||||
}
|
||||
|
|
@ -103,9 +112,9 @@ func (r *Runner) QualityReport() (*QualityReport, error) {
|
|||
return nil, err
|
||||
}
|
||||
// Total = the SHIPPING units (the manifest re-chunk, $0), matching status/export + the per-unit
|
||||
// BookResult (R1): under the wave model the editor's final text is per EDIT UNIT, so ProcessedChunks
|
||||
// (the lastStage rows, one per unit leader) and TotalChunks agree at unit granularity. The per-unit
|
||||
// KPI/echo/strip signals land on the leader's edit row; a non-leader member's ChunkQuality row carries
|
||||
// BookResult (R1): under the wave model the editor's final text is per EDIT UNIT, so ProcessedUnits
|
||||
// (the lastStage rows, one per unit leader) and TotalUnits agree at unit granularity. The per-unit
|
||||
// KPI/strip signals land on the leader's edit row; a non-leader member's ChunkQuality row carries
|
||||
// only its draft-side signals (trust-gated) with a 0 structural KPI — observability, never a gate.
|
||||
chunks, err := r.bookChunks()
|
||||
if err != nil {
|
||||
|
|
@ -113,35 +122,24 @@ func (r *Runner) QualityReport() (*QualityReport, error) {
|
|||
}
|
||||
units := r.outputUnits(chunks)
|
||||
// GHOST guard (parity with Export/Status): a stored row whose unit-leader key is NOT in the current
|
||||
// manifest (source shrank since the run) is a ghost — dropping it keeps ProcessedChunks ≤ TotalChunks
|
||||
// and the echo/strip rates over live units only, instead of mixing in stale leader rows.
|
||||
// manifest (source shrank since the run) is a ghost — dropping it keeps ProcessedUnits ≤ TotalUnits.
|
||||
inManifest := map[chunkKey]bool{}
|
||||
for _, u := range units {
|
||||
inManifest[chunkKey{u.Chapter, u.FirstChunkIdx}] = true
|
||||
}
|
||||
|
||||
// c-lite (D39.17-fix): the wave editor drops a flagged member and edits the clean remainder, so the leader
|
||||
// edit row is DispOK even though a member's draft echoed/stripped. Re-derive the dropped member's reason per
|
||||
// unit (like export/status) so the echo/strip RATES still count it — else a unit RECOVERED by editing the
|
||||
// clean remainder would vanish from EchoRate/CosmeticStripRate the owner reads for the acceptance planka.
|
||||
// A retrieval_state row / a draft echo is keyed per DRAFT chunk, so it is live iff its chunk is still in
|
||||
// the manifest; a chunk that left the source is a ghost.
|
||||
liveChunks := map[chunkKey]bool{}
|
||||
for _, ch := range chunks {
|
||||
liveChunks[chunkKey{ch.Chapter, ch.ChunkIdx}] = true
|
||||
}
|
||||
// Wave stage-name sets classify a stored chunk_status row by wave for the split echo metric (D39.18
|
||||
// owner decision): echo_draft is counted DIRECTLY from the draft rows (no c-lite per-unit re-derivation
|
||||
// — a dropped member's own draft row already carries cjk_artifact), echo_edit from the edit rows.
|
||||
draftStageNames := stageNameSet(r.waveStagesIndexed(waveDraft))
|
||||
memberDraftFlag := map[chunkKey]string{}
|
||||
for _, cs := range statuses {
|
||||
if draftStageNames[cs.Stage] && cs.Disposition == string(DispFlagged) {
|
||||
memberDraftFlag[chunkKey{cs.Chapter, cs.ChunkIdx}] = cs.FlagReason
|
||||
}
|
||||
}
|
||||
droppedReasonByLeader := map[chunkKey]string{}
|
||||
for _, u := range units {
|
||||
for _, m := range u.Members {
|
||||
if reason, dropped := memberDraftFlag[chunkKey{m.Chapter, m.ChunkIdx}]; dropped {
|
||||
droppedReasonByLeader[chunkKey{u.Chapter, u.FirstChunkIdx}] = reason
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
editStageNames := stageNameSet(r.waveStagesIndexed(waveEdit))
|
||||
|
||||
rep := &QualityReport{BookID: r.Book.BookID, TotalChunks: len(units)}
|
||||
rep := &QualityReport{BookID: r.Book.BookID, TotalUnits: len(units)}
|
||||
byChunk := map[chunkKey]*ChunkQuality{}
|
||||
order := []chunkKey{}
|
||||
chunkOf := func(k chunkKey) *ChunkQuality {
|
||||
|
|
@ -157,18 +155,13 @@ func (r *Runner) QualityReport() (*QualityReport, error) {
|
|||
// The glossary post-check GATE flips a chunk to withheld (flagged glossary_miss, empty export) at
|
||||
// the CHUNK level without a chunk_status row (like Export/Status re-derive it). F5 (D39.4): the
|
||||
// structural KPI must EXCLUDE these — their export is "", so counting their (withheld) text in
|
||||
// TextChunks/KPI diverges from what `tmctl export` and `translate` actually ship.
|
||||
// TextUnits/KPI diverges from what `tmctl export` and `translate` actually ship.
|
||||
gateOn := r.Pipeline.Gates.Glossary.PostcheckGate
|
||||
withheld := map[chunkKey]bool{}
|
||||
|
||||
// Aggregate the stored retrieval-state signals (glossary consistency, style breakdown, trust-gated).
|
||||
// A retrieval_state row is keyed per DRAFT chunk, so it is live iff its chunk is still in the manifest;
|
||||
// a chunk that left the source is a ghost. (A leader row also carries the unit's post-check; if the
|
||||
// leader survives, so does the unit.)
|
||||
liveChunks := map[chunkKey]bool{}
|
||||
for _, ch := range chunks {
|
||||
liveChunks[chunkKey{ch.Chapter, ch.ChunkIdx}] = true
|
||||
}
|
||||
// A retrieval_state row is keyed per DRAFT chunk (live iff its chunk is still in the manifest); a leader
|
||||
// row also carries the unit's post-check, so if the leader survives, so does the unit.
|
||||
for _, rs := range states {
|
||||
if !liveChunks[chunkKey{rs.Chapter, rs.ChunkIdx}] {
|
||||
continue // ghost retrieval_state row (chunk dropped from source)
|
||||
|
|
@ -194,7 +187,35 @@ func (r *Runner) QualityReport() (*QualityReport, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// The exported-text chunks (the FINAL stage's row): echo/CJK flag rates + the structural KPI.
|
||||
// Split echo by stage (D39.18 owner decision): echo_draft over the DRAFT-stage rows (translator quality,
|
||||
// INCLUDING a c-lite dropped member — its own draft row carries cjk_artifact, so no per-unit re-derivation
|
||||
// is needed), echo_edit over the EDIT-stage rows (delivered quality — only the EDITOR's own echo counts,
|
||||
// a skipped edit row is a draft echo not an editor one). Ghost-guarded like the rest (live chunks / units).
|
||||
var draftRows, draftEcho, editRows, editEcho int
|
||||
for _, cs := range statuses {
|
||||
k := chunkKey{cs.Chapter, cs.ChunkIdx}
|
||||
switch {
|
||||
case draftStageNames[cs.Stage] && liveChunks[k]:
|
||||
draftRows++
|
||||
if cs.FlagReason == string(FlagCJKArtifact) {
|
||||
draftEcho++ // the translator echoed CJK (a flagged draft row), incl. a c-lite dropped member
|
||||
}
|
||||
case editStageNames[cs.Stage] && inManifest[k]:
|
||||
editRows++
|
||||
if cs.Disposition == string(DispFlagged) && cs.FlagReason == string(FlagCJKArtifact) {
|
||||
editEcho++ // the EDITOR's OWN output echoed (a skipped edit row means the drafts echoed, not the editor)
|
||||
}
|
||||
}
|
||||
}
|
||||
rep.EchoDraftChunks, rep.EchoEditUnits = draftEcho, editEcho
|
||||
if draftRows > 0 {
|
||||
rep.EchoDraftRate = float64(draftEcho) / float64(draftRows)
|
||||
}
|
||||
if editRows > 0 {
|
||||
rep.EchoEditRate = float64(editEcho) / float64(editRows)
|
||||
}
|
||||
|
||||
// The exported-text units (the FINAL stage's row): the cosmetic-strip rate + the structural KPI.
|
||||
lastStage := ""
|
||||
if n := len(r.Pipeline.Stages); n > 0 {
|
||||
lastStage = r.Pipeline.Stages[n-1].Name
|
||||
|
|
@ -204,39 +225,21 @@ func (r *Runner) QualityReport() (*QualityReport, error) {
|
|||
if cs.Stage != lastStage || !inManifest[k] {
|
||||
continue // the final verdict lives on the final stage's row (per unit); drop ghost leader rows
|
||||
}
|
||||
// Every chunk that REACHED the final stage has exactly one lastStage row (ok, cosmetic-strip,
|
||||
// or skipped-because-an-upstream-stage-flagged). This is the common rate denominator so the
|
||||
// echo/CJK rates are a bounded fraction of processed chunks (an echo chunk is a skipped final
|
||||
// row, disjoint from the exported-text set — dividing by TextChunks alone could exceed 100%).
|
||||
rep.ProcessedChunks++
|
||||
if cs.FlagReason == string(FlagCJKArtifact) {
|
||||
rep.EchoChunks++
|
||||
chunkOf(k) // ensure the chunk row exists even if it has no retrieval-state signals
|
||||
}
|
||||
// Every unit that REACHED the final stage has exactly one lastStage row (ok, cosmetic-strip, or
|
||||
// skipped-because-a-member-flagged) — the strip-rate denominator.
|
||||
rep.ProcessedUnits++
|
||||
if cs.FlagReason == string(FlagSanitizerStripped) {
|
||||
rep.CosmeticStripChunks++ // a stripped chunk carried a markdown OR CJK cosmetic leak (F6)
|
||||
}
|
||||
// c-lite: a DispOK leader edit row whose unit dropped a member counts toward the same rates by the
|
||||
// dropped member's reason — the echo/strip happened in a real (dropped) draft, recovered by editing the
|
||||
// rest. Disjoint from the two checks above (they need a flagged edit row; this needs a DispOK one).
|
||||
if cs.Disposition == string(DispOK) {
|
||||
switch droppedReasonByLeader[k] {
|
||||
case string(FlagCJKArtifact):
|
||||
rep.EchoChunks++
|
||||
chunkOf(k)
|
||||
case string(FlagSanitizerStripped):
|
||||
rep.CosmeticStripChunks++
|
||||
}
|
||||
rep.CosmeticStripUnits++ // a stripped unit carried a markdown OR CJK cosmetic leak (F6)
|
||||
}
|
||||
// Structural KPI: recompute over the exported final text (ok, or the cosmetic-stripped export).
|
||||
if cs.FinalHash == "" {
|
||||
continue
|
||||
}
|
||||
if cs.Disposition != string(DispOK) && cs.FlagReason != string(FlagSanitizerStripped) {
|
||||
continue // a dropped chunk exported nothing
|
||||
continue // a dropped unit exported nothing
|
||||
}
|
||||
if withheld[k] {
|
||||
continue // F5: the glossary gate withheld this chunk's text — it exports nothing
|
||||
continue // F5: the glossary gate withheld this unit's text — it exports nothing
|
||||
}
|
||||
cp, cperr := r.Store.GetCheckpoint(cs.FinalHash)
|
||||
if cperr != nil {
|
||||
|
|
@ -250,15 +253,14 @@ func (r *Runner) QualityReport() (*QualityReport, error) {
|
|||
q.NarrativeSentences, q.NarrativeParagraphs = sent, para
|
||||
rep.NarrativeSentences += sent
|
||||
rep.NarrativeParagraphs += para
|
||||
rep.TextChunks++
|
||||
rep.TextUnits++
|
||||
}
|
||||
|
||||
if rep.NarrativeParagraphs > 0 {
|
||||
rep.MeanSentPerNarrPara = float64(rep.NarrativeSentences) / float64(rep.NarrativeParagraphs)
|
||||
}
|
||||
if rep.ProcessedChunks > 0 {
|
||||
rep.CosmeticStripRate = float64(rep.CosmeticStripChunks) / float64(rep.ProcessedChunks)
|
||||
rep.EchoRate = float64(rep.EchoChunks) / float64(rep.ProcessedChunks)
|
||||
if rep.ProcessedUnits > 0 {
|
||||
rep.CosmeticStripRate = float64(rep.CosmeticStripUnits) / float64(rep.ProcessedUnits)
|
||||
}
|
||||
for _, k := range order {
|
||||
rep.Chunks = append(rep.Chunks, *byChunk[k])
|
||||
|
|
|
|||
|
|
@ -62,12 +62,12 @@ func TestQualityReportAggregates(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("QualityReport: %v", err)
|
||||
}
|
||||
if q.TotalChunks != 1 || q.TextChunks != 1 || q.ProcessedChunks != 1 {
|
||||
t.Fatalf("chunk counts: total=%d processed=%d text=%d, want 1/1/1", q.TotalChunks, q.ProcessedChunks, q.TextChunks)
|
||||
if q.TotalUnits != 1 || q.TextUnits != 1 || q.ProcessedUnits != 1 {
|
||||
t.Fatalf("unit counts: total=%d processed=%d text=%d, want 1/1/1", q.TotalUnits, q.ProcessedUnits, q.TextUnits)
|
||||
}
|
||||
// Rates are bounded [0,1] over ProcessedChunks (the disjoint-set bug fix).
|
||||
if q.EchoRate < 0 || q.EchoRate > 1 || q.CosmeticStripRate < 0 || q.CosmeticStripRate > 1 {
|
||||
t.Errorf("rates must be within [0,1], got echo=%.3f cosmetic=%.3f", q.EchoRate, q.CosmeticStripRate)
|
||||
// Rates are bounded [0,1].
|
||||
if q.EchoDraftRate < 0 || q.EchoDraftRate > 1 || q.EchoEditRate < 0 || q.EchoEditRate > 1 || q.CosmeticStripRate < 0 || q.CosmeticStripRate > 1 {
|
||||
t.Errorf("rates must be within [0,1], got echo_draft=%.3f echo_edit=%.3f cosmetic=%.3f", q.EchoDraftRate, q.EchoEditRate, q.CosmeticStripRate)
|
||||
}
|
||||
if q.NarrativeSentences != 3 || q.NarrativeParagraphs != 2 {
|
||||
t.Errorf("structural KPI = %d sent / %d para, want 3/2", q.NarrativeSentences, q.NarrativeParagraphs)
|
||||
|
|
@ -75,8 +75,8 @@ func TestQualityReportAggregates(t *testing.T) {
|
|||
if q.MeanSentPerNarrPara != 1.5 {
|
||||
t.Errorf("mean sentences/narrative-paragraph = %.3f, want 1.5", q.MeanSentPerNarrPara)
|
||||
}
|
||||
// A clean run: no glossary misses, no echo, no CJK strip, no trust-gated, no dialogue-dash issue.
|
||||
if q.GlossaryMisses != 0 || q.EchoChunks != 0 || q.CosmeticStripChunks != 0 || q.TrustGated != 0 || q.DialogueDashFlags != 0 {
|
||||
// A clean run: no glossary misses, no echo (draft or edit), no CJK strip, no trust-gated, no dash issue.
|
||||
if q.GlossaryMisses != 0 || q.EchoDraftChunks != 0 || q.EchoEditUnits != 0 || q.CosmeticStripUnits != 0 || q.TrustGated != 0 || q.DialogueDashFlags != 0 {
|
||||
t.Errorf("clean run must have zero defect signals, got %+v", q)
|
||||
}
|
||||
if len(q.Chunks) != 1 || q.Chunks[0].NarrativeParagraphs != 2 {
|
||||
|
|
@ -106,13 +106,13 @@ func TestQualityReportExcludesGateWithheld(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("QualityReport: %v", err)
|
||||
}
|
||||
// The single chunk is gate-withheld → processed but NOT text-bearing.
|
||||
if q.ProcessedChunks != 1 {
|
||||
t.Fatalf("the withheld chunk still reached the final stage: processed=%d", q.ProcessedChunks)
|
||||
// The single unit is gate-withheld → processed but NOT text-bearing.
|
||||
if q.ProcessedUnits != 1 {
|
||||
t.Fatalf("the withheld unit still reached the final stage: processed=%d", q.ProcessedUnits)
|
||||
}
|
||||
if q.TextChunks != 0 || q.NarrativeSentences != 0 || q.NarrativeParagraphs != 0 {
|
||||
t.Errorf("gate-withheld chunk must be excluded from the KPI, got text=%d sent=%d para=%d",
|
||||
q.TextChunks, q.NarrativeSentences, q.NarrativeParagraphs)
|
||||
if q.TextUnits != 0 || q.NarrativeSentences != 0 || q.NarrativeParagraphs != 0 {
|
||||
t.Errorf("gate-withheld unit must be excluded from the KPI, got text=%d sent=%d para=%d",
|
||||
q.TextUnits, q.NarrativeSentences, q.NarrativeParagraphs)
|
||||
}
|
||||
// It IS visible as a glossary miss (the observability signal is kept).
|
||||
if q.GlossaryMisses == 0 {
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ func TestReadOnlyRunnerWorksDuringLiveRun(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if rep.TotalChunks != 1 || rep.Done != 1 || rep.Flagged != 0 {
|
||||
if rep.TotalUnits != 1 || rep.Done != 1 || rep.Flagged != 0 {
|
||||
t.Fatalf("status projection over a live-locked book = %+v", rep)
|
||||
}
|
||||
if rep.CommittedUSD == 0 {
|
||||
|
|
@ -69,7 +69,7 @@ func TestReadOnlyRunnerFirstTouchCreates(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if rep.TotalChunks != 1 || rep.Done != 0 || rep.Pending != 1 {
|
||||
if rep.TotalUnits != 1 || rep.Done != 0 || rep.Pending != 1 {
|
||||
t.Fatalf("pre-run status = %+v", rep)
|
||||
}
|
||||
if rec.count() != 0 {
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ func TestSeedLintEmittedMinedDelta(t *testing.T) {
|
|||
// the bank-mining reseed.
|
||||
chunks := mchunks(strings.Repeat("龙公来到青茅山。龙公很强。青茅山很高。", 4))
|
||||
seed := []store.GlossaryEntry{{Src: "青茅山", Dst: "гора Цинмао", Type: "place", Status: "approved", Source: "seed"}}
|
||||
mined := MineBank(chunks, smallContrast(t), seed, frozenMinerConfig(), testLangPack(t))
|
||||
mined := MineBank(chunks, smallContrast(t), seed, nil, frozenMinerConfig(), testLangPack(t))
|
||||
yamlStr, err := MinedDeltaYAML(mined)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
|
|||
|
|
@ -537,6 +537,11 @@ func (r *Runner) baseRequestLog(st config.Stage, ch Chunk, model, reqHash string
|
|||
// telemetry, not the resume authority (chunk_status/checkpoints are), so a failed
|
||||
// write must not abort the pipeline — but a jobs table silently stuck in
|
||||
// running/failed desyncs the status/redrive read-models with zero trace.
|
||||
//
|
||||
// NOTE (R1 wave model, D39.17-errata nit): a job row is keyed (book, chapter, stage), so the parallel
|
||||
// members of a multi-chunk edit unit — and the two waves of one chapter — all THRASH one shared row's
|
||||
// status. This is advisory-only: NOTHING gates on jobs.status (the read-models resolve state from
|
||||
// chunk_status/checkpoints, R1 post-landing review §1), so the thrash is cosmetic telemetry, not a bug.
|
||||
func (r *Runner) setJobStatus(ctx context.Context, jobID int64, status string) {
|
||||
if err := r.Store.SetJobStatus(jobID, status); err != nil {
|
||||
r.Log.WarnContext(ctx, "job status update failed (non-fatal; jobs table may lag chunk_status)",
|
||||
|
|
|
|||
|
|
@ -33,17 +33,17 @@ const (
|
|||
// reason, a pass|attention|fail verdict (exp07 chapter rule) and the chapter's spend.
|
||||
type ChapterPassport struct {
|
||||
Chapter int `json:"chapter"`
|
||||
ChunksTotal int `json:"chunks_total"`
|
||||
ChunksDone int `json:"chunks_done"`
|
||||
ChunksFlagged int `json:"chunks_flagged"`
|
||||
ChunksInFlight int `json:"chunks_in_progress"`
|
||||
ChunksPending int `json:"chunks_pending"`
|
||||
UnitsTotal int `json:"units_total"`
|
||||
UnitsDone int `json:"units_done"`
|
||||
UnitsFlagged int `json:"units_flagged"`
|
||||
UnitsInProgress int `json:"units_in_progress"`
|
||||
UnitsPending int `json:"units_pending"`
|
||||
StagesSkipped int `json:"stages_skipped"` // per-stage skip count (downstream of a flag)
|
||||
Escalations int `json:"escalations"`
|
||||
PostcheckMisses int `json:"postcheck_misses"` // confirmed post-check misses (retrieval_state)
|
||||
StyleFlags int `json:"style_flags"` // cheap style/number gate hits (observability, not a disposition)
|
||||
WorstFlagReason string `json:"worst_flag_reason,omitempty"`
|
||||
Verdict string `json:"verdict"` // pass | attention | fail (exp07: 0/1/≥2 flagged chunks)
|
||||
Verdict string `json:"verdict"` // pass | attention | fail (exp07: 0/1/≥2 flagged units)
|
||||
CostUSD float64 `json:"cost_usd"`
|
||||
}
|
||||
|
||||
|
|
@ -62,7 +62,7 @@ type StatusReport struct {
|
|||
ConfigDrift bool `json:"config_drift"`
|
||||
CurrentSnapshot string `json:"current_snapshot,omitempty"`
|
||||
|
||||
TotalChunks int `json:"total_chunks"`
|
||||
TotalUnits int `json:"total_units"`
|
||||
Done int `json:"done"`
|
||||
InProgress int `json:"in_progress"`
|
||||
Flagged int `json:"flagged"`
|
||||
|
|
@ -228,7 +228,7 @@ func (r *Runner) Status(ctx context.Context) (*StatusReport, error) {
|
|||
// written as a chunk_status row (every stage stays DispOK). Without accounting for it here, status
|
||||
// would report a gate-flagged unit as done/pass while `tmctl translate` exits 2 (finding #2).
|
||||
gateOn := r.Pipeline.Gates.Glossary.PostcheckGate
|
||||
rep := &StatusReport{BookID: r.Book.BookID, TotalChunks: len(units)}
|
||||
rep := &StatusReport{BookID: r.Book.BookID, TotalUnits: len(units)}
|
||||
passports := map[int]*ChapterPassport{}
|
||||
var chapterOrder []int
|
||||
var processedCost float64 // spend of PROCESSED units (done+flagged) — the projection base (finding #7)
|
||||
|
|
@ -240,7 +240,7 @@ func (r *Runner) Status(ctx context.Context) (*StatusReport, error) {
|
|||
passports[u.Chapter] = p
|
||||
chapterOrder = append(chapterOrder, u.Chapter)
|
||||
}
|
||||
p.ChunksTotal++
|
||||
p.UnitsTotal++
|
||||
leader := chunkKey{u.Chapter, u.FirstChunkIdx}
|
||||
var rows []store.ChunkStatus
|
||||
for _, m := range u.Members {
|
||||
|
|
@ -270,19 +270,19 @@ func (r *Runner) Status(ctx context.Context) (*StatusReport, error) {
|
|||
switch state {
|
||||
case ChunkDone:
|
||||
rep.Done++
|
||||
p.ChunksDone++
|
||||
p.UnitsDone++
|
||||
case ChunkFlagged:
|
||||
rep.Flagged++
|
||||
p.ChunksFlagged++
|
||||
p.UnitsFlagged++
|
||||
if p.WorstFlagReason == "" || flagReasonSeverity(reason) < flagReasonSeverity(p.WorstFlagReason) {
|
||||
p.WorstFlagReason = reason
|
||||
}
|
||||
case ChunkInProgress:
|
||||
rep.InProgress++
|
||||
p.ChunksInFlight++
|
||||
p.UnitsInProgress++
|
||||
case ChunkPending:
|
||||
rep.Pending++
|
||||
p.ChunksPending++
|
||||
p.UnitsPending++
|
||||
}
|
||||
if state == ChunkDone || state == ChunkFlagged {
|
||||
processedCost += cost // a fully-attempted unit's cost feeds the projection
|
||||
|
|
@ -295,9 +295,9 @@ func (r *Runner) Status(ctx context.Context) (*StatusReport, error) {
|
|||
for _, n := range chapterOrder {
|
||||
p := passports[n]
|
||||
switch {
|
||||
case p.ChunksFlagged >= 2:
|
||||
case p.UnitsFlagged >= 2:
|
||||
p.Verdict = "fail"
|
||||
case p.ChunksFlagged == 1:
|
||||
case p.UnitsFlagged == 1:
|
||||
p.Verdict = "attention"
|
||||
default:
|
||||
p.Verdict = "pass"
|
||||
|
|
@ -305,8 +305,8 @@ func (r *Runner) Status(ctx context.Context) (*StatusReport, error) {
|
|||
rep.Chapters = append(rep.Chapters, *p)
|
||||
}
|
||||
|
||||
if rep.TotalChunks > 0 {
|
||||
rep.PercentDone = 100 * float64(rep.Done) / float64(rep.TotalChunks)
|
||||
if rep.TotalUnits > 0 {
|
||||
rep.PercentDone = 100 * float64(rep.Done) / float64(rep.TotalUnits)
|
||||
}
|
||||
|
||||
// Per-wave snapshot drift (finding #3, wave-aware): the rows carry draft-wave snapshot (draft) + edit-wave snapshot
|
||||
|
|
@ -380,7 +380,7 @@ func (r *Runner) Status(ctx context.Context) (*StatusReport, error) {
|
|||
// which would over-estimate (finding #7). The clean average × total is the honest estimate.
|
||||
processed := rep.Done + rep.Flagged
|
||||
if processed > 0 {
|
||||
rep.ProjectedBookUSD = processedCost / float64(processed) * float64(rep.TotalChunks)
|
||||
rep.ProjectedBookUSD = processedCost / float64(processed) * float64(rep.TotalUnits)
|
||||
}
|
||||
|
||||
// ETA (secondary): mean fresh-call throughput × remaining processing. No synthetic bar.
|
||||
|
|
@ -394,7 +394,7 @@ func (r *Runner) Status(ctx context.Context) (*StatusReport, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
remaining := rep.TotalChunks - processed
|
||||
remaining := rep.TotalUnits - processed
|
||||
if processed > 0 && freshCalls > 0 && remaining > 0 {
|
||||
meanMSPerChunk := float64(totalMS) / float64(processed)
|
||||
rep.ETASeconds = meanMSPerChunk * float64(remaining) / 1000
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ func TestStatusAndRedrive(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if rep.TotalChunks != 2 || rep.Done != 1 || rep.Flagged != 1 || rep.Pending != 0 || rep.InProgress != 0 {
|
||||
if rep.TotalUnits != 2 || rep.Done != 1 || rep.Flagged != 1 || rep.Pending != 0 || rep.InProgress != 0 {
|
||||
t.Fatalf("status counts wrong: %+v", rep)
|
||||
}
|
||||
if math.Abs(rep.PercentDone-50) > 1e-9 {
|
||||
|
|
@ -60,10 +60,10 @@ func TestStatusAndRedrive(t *testing.T) {
|
|||
if len(rep.Chapters) != 2 {
|
||||
t.Fatalf("want 2 chapter passports, got %d", len(rep.Chapters))
|
||||
}
|
||||
if rep.Chapters[0].Verdict != "attention" || rep.Chapters[0].WorstFlagReason != string(FlagSoftRefusal) || rep.Chapters[0].ChunksFlagged != 1 {
|
||||
if rep.Chapters[0].Verdict != "attention" || rep.Chapters[0].WorstFlagReason != string(FlagSoftRefusal) || rep.Chapters[0].UnitsFlagged != 1 {
|
||||
t.Errorf("ch1 passport = %+v (want attention/soft_refusal/1 flagged)", rep.Chapters[0])
|
||||
}
|
||||
if rep.Chapters[1].Verdict != "pass" || rep.Chapters[1].ChunksDone != 1 {
|
||||
if rep.Chapters[1].Verdict != "pass" || rep.Chapters[1].UnitsDone != 1 {
|
||||
t.Errorf("ch2 passport = %+v (want pass/1 done)", rep.Chapters[1])
|
||||
}
|
||||
wantCommitted := 3 * fakeCallUSD
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
==== run 1 (fresh) ====
|
||||
snapshot_draft: 914c9434d646ec173dbafbac5f2355d9bd4920bf9e3499f237a97c046d82c84d
|
||||
snapshot_draft_payload: {"brief_hash":"c2021b1b5e6c20f6084e425b9a70bdc5f682801be347a57eb1c591deb8cab0df","chunker_version":"chunker-v5-output-budget-editunit+u15.0.0","estimator_version":"estimator-v0+u15.0.0","max_tokens_policy":"maxtok-v1-double-per-attempt","classifier_version":"classify-v1-refusal+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-v2-editor-src2dst+dc3-gender","memory_version":"173e87fc011e832c108711097eeffc05db95ab56eafe2ef913b43829505cdfdd","postcheck_gate":false,"coverage":{"enabled":false},"style_check_version":"cheapgate-v3-dc-checkers+u15.0.0","sanitizer":{"enabled":true,"version":"sanitizer-v6"},"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: 6fe7e9463b2bb3a93c602468201d650ffcc489c02042099351ac8e66be6f17ed
|
||||
snapshot_edit_payload: {"brief_hash":"c2021b1b5e6c20f6084e425b9a70bdc5f682801be347a57eb1c591deb8cab0df","chunker_version":"chunker-v5-output-budget-editunit+u15.0.0","estimator_version":"estimator-v0+u15.0.0","max_tokens_policy":"maxtok-v1-double-per-attempt","classifier_version":"classify-v1-refusal+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-v2-editor-src2dst+dc3-gender","memory_version":"740aefe880a34396172775751d41d0e9ef136d053f3200a4f94391fa72aee7e4","postcheck_gate":false,"coverage":{"enabled":false},"style_check_version":"cheapgate-v3-dc-checkers+u15.0.0","sanitizer":{"enabled":true,"version":"sanitizer-v6"},"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: b1279edc077f8f26c67e4e94c01eb29e02e0f706d4d18f267519bbb58f2e474a
|
||||
snapshot_draft_payload: {"brief_hash":"c2021b1b5e6c20f6084e425b9a70bdc5f682801be347a57eb1c591deb8cab0df","chunker_version":"chunker-v5-output-budget-editunit+u15.0.0","estimator_version":"estimator-v0+u15.0.0","max_tokens_policy":"maxtok-v1-double-per-attempt","classifier_version":"classify-v1-refusal+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-v2-editor-src2dst+dc3-gender","memory_version":"bd451833d4ebb8a0ca07caf8deda76f027e1f42b78654987a22d215acf955eb7","postcheck_gate":false,"coverage":{"enabled":false},"style_check_version":"cheapgate-v3-dc-checkers+u15.0.0","sanitizer":{"enabled":true,"version":"sanitizer-v6"},"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: 66a990322f96d7f8f6d4783c63dbee75833b532e31d6a95d2de81394880d0a41
|
||||
snapshot_edit_payload: {"brief_hash":"c2021b1b5e6c20f6084e425b9a70bdc5f682801be347a57eb1c591deb8cab0df","chunker_version":"chunker-v5-output-budget-editunit+u15.0.0","estimator_version":"estimator-v0+u15.0.0","max_tokens_policy":"maxtok-v1-double-per-attempt","classifier_version":"classify-v1-refusal+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-v2-editor-src2dst+dc3-gender","memory_version":"0a48d6ed9409de09f514af91aebdd622637621c9dc9464e8898c238791033d91","postcheck_gate":false,"coverage":{"enabled":false},"style_check_version":"cheapgate-v3-dc-checkers+u15.0.0","sanitizer":{"enabled":true,"version":"sanitizer-v6"},"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: 740aefe880a34396172775751d41d0e9ef136d053f3200a4f94391fa72aee7e4
|
||||
base_memory_version: 173e87fc011e832c108711097eeffc05db95ab56eafe2ef913b43829505cdfdd
|
||||
memory_version: 0a48d6ed9409de09f514af91aebdd622637621c9dc9464e8898c238791033d91
|
||||
base_memory_version: bd451833d4ebb8a0ca07caf8deda76f027e1f42b78654987a22d215acf955eb7
|
||||
-- book result --
|
||||
chunks=8 flagged=4 exit=2 total_usd=0.04003999999999999
|
||||
chunk ch1/0 disposition=ok flag="" final_text="ОТРЕДАКТИРОВАННЫЙ ПЕРЕВОД 22c30f20dea6. Судзуки шёл по коридорам Академии магии." cost=0.00546
|
||||
|
|
@ -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-утечка в ru-выходе: 特产"
|
||||
stage_text="" recovered="Судзуки нашёл древний камень на каменном алтаре."
|
||||
-- chunk_status --
|
||||
ch1/0 draft snap_match=true content_hash=331c6f32c7f3ed15447e2639a2621d24a180e561a2aee92115324f492db5de3e disp=ok flag="" attempts=1 final_hash=96f55c8caeec48506a73141758fe8e819c9da32c9f6d969e02b83057cc2a7af3 cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch1/0 edit snap_match=true content_hash=8a4e763f79c94ad20a1bac0231a8c1376ccd4f5a71c53cdfcb4de146c0b48b42 disp=ok flag="" attempts=1 final_hash=dd9978846ec37ddde3e4db428582661271fd51e8cccbce1374fd2200800e9614 cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch1/1 draft snap_match=true content_hash=12fe3ccdf062f43e011363a33ff85c9b6842a8f23d790991fe9afab51a8becc8 disp=ok flag="" attempts=1 final_hash=c9b9807b0279cb186cfae7e1255b2ea6e608b994b3429df9e7cb48f40c21b520 cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch2/0 draft snap_match=true content_hash=2693325bc747687acf090bc263fe8c17783726b949c6d8a2d072da3401be6827 disp=ok flag="" attempts=1 final_hash=999ab8cd87c3505efaade5cc30b7f2d38a4e3fc6183250f34906e5c2db84f078 cost=0.00728 escalated=true esc_model="fake-fallback" detail=""
|
||||
ch2/0 edit snap_match=true content_hash=5a5397d41bcfa9326b5fe67c70aca8d9ef14ef89af2dbe1a758e25d364226fc5 disp=ok flag="" attempts=1 final_hash=894afeaecfc7bef655d937d01a1c69b920c9500d10e4de9d1095f4391a55adac cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch1/0 draft snap_match=true content_hash=331c6f32c7f3ed15447e2639a2621d24a180e561a2aee92115324f492db5de3e disp=ok flag="" attempts=1 final_hash=365abc01adb22d047a3865c79231c2669f7db960fd07069d46547d562cfc537b cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch1/0 edit snap_match=true content_hash=8a4e763f79c94ad20a1bac0231a8c1376ccd4f5a71c53cdfcb4de146c0b48b42 disp=ok flag="" attempts=1 final_hash=42e10469327dcf7d8409cda457a7eff626795a709ff0b37f3de260d7ca11986a cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch1/1 draft snap_match=true content_hash=12fe3ccdf062f43e011363a33ff85c9b6842a8f23d790991fe9afab51a8becc8 disp=ok flag="" attempts=1 final_hash=6f883d099cd0924eab5db1ef3eb730aa50429e719ffeea95df93e33042e9fcc0 cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch2/0 draft snap_match=true content_hash=2693325bc747687acf090bc263fe8c17783726b949c6d8a2d072da3401be6827 disp=ok flag="" attempts=1 final_hash=f7a858a11f0890da139789d27bea23b1c33c83ef989f2d07dc51d4b238d5e16e cost=0.00728 escalated=true esc_model="fake-fallback" detail=""
|
||||
ch2/0 edit snap_match=true content_hash=5a5397d41bcfa9326b5fe67c70aca8d9ef14ef89af2dbe1a758e25d364226fc5 disp=ok flag="" attempts=1 final_hash=ff45d79e2d08b57328491e1ac5e0b6b67ba883346c44806b7a63e7adddcf65bf cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch3/0 draft snap_match=true content_hash=733fc6a89215efe112cfadd1d1fb361bb8a7ab1013994248a49da0dbd137548e disp=flagged flag="hard_refusal" attempts=1 final_hash= cost=0.00728 escalated=true esc_model="fake-fallback" 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=ac14e02f697c4fc4963946365a118afb5e8ba974c0d3c6781aac17d403dec5fd cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch4/0 edit snap_match=true content_hash=2854dfe1648b0cf0cbb683cb7ce94c5eedc9223b2ce20c99dc008c7add6317ef disp=ok flag="" attempts=1 final_hash=5db400bde72cc0e7991f5860a524a73db4b511c683ef918cad505ffba3a6e314 cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch5/0 draft snap_match=true content_hash=d8aba42cabb41853a81875ebc725bb516dc2412e29de3a03bc1a68e90f8cf157 disp=ok flag="" attempts=1 final_hash=7a7a6a7c55f5dcd00a80d04e8da5b4bc0f4b9bca8a33baa24397b7dfa9424e20 cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch5/0 edit snap_match=true content_hash=82b05e4c3f5ca8432d7eaa7d7d63c4a7766f47a08398953c9a9bc3dc8437039b disp=ok flag="" attempts=1 final_hash=3fdb393a5e9bcba9869c7e0125d89e07f8bf64bb33c0098d999a9dcf5f5285ae cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch6/0 draft snap_match=true content_hash=9375b05780724cc354eba15a595da9a5f964219c0887042484256eed11fb566f disp=ok flag="" attempts=1 final_hash=a96545d035e16b72a6abce2274f2ea4664399da63b9aaf88e5dfb3a96af20e2d cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch4/0 draft snap_match=true content_hash=7aabdcb89dea365c95363b2662144ca1a10b307d377f47a8e7cc19b5b872d287 disp=ok flag="" attempts=1 final_hash=6fb9e932865b46557a1f5e4a0852a6fe5b2ed728e54d5704801840f1dbe89ccb cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch4/0 edit snap_match=true content_hash=2854dfe1648b0cf0cbb683cb7ce94c5eedc9223b2ce20c99dc008c7add6317ef disp=ok flag="" attempts=1 final_hash=5b1f425d363f501da5832d570ebeb036a8d780378abf3e92cdded87c247b9b6e cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch5/0 draft snap_match=true content_hash=d8aba42cabb41853a81875ebc725bb516dc2412e29de3a03bc1a68e90f8cf157 disp=ok flag="" attempts=1 final_hash=03ed6d252620e8763a0afc2d535e1b6e1cf97ca1145a66aa08ea199ef7102c04 cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch5/0 edit snap_match=true content_hash=82b05e4c3f5ca8432d7eaa7d7d63c4a7766f47a08398953c9a9bc3dc8437039b disp=ok flag="" attempts=1 final_hash=3367114609df41762ff6f64b57de90f85b6f1992987ebf875bc64e028a6a0a4b cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch6/0 draft snap_match=true content_hash=9375b05780724cc354eba15a595da9a5f964219c0887042484256eed11fb566f disp=ok flag="" attempts=1 final_hash=1efa0b7620e14fa8cdff89a198efe113e2196a4a48717b1dfacfc9bc878d2ae8 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="ведущая служебная преамбула: Вот перевод фрагмента:"
|
||||
ch7/0 draft snap_match=true content_hash=d90c335cba3d1547a22bb3f656443cb432f84c12a577d43509cfe472cf86a2ac disp=ok flag="" attempts=1 final_hash=9af233395ddde718f61b1bbb4709095a43ea3c4e146ca10990e821607b92bca5 cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch7/0 edit snap_match=true content_hash=e12521b6eb6dbc2cd35758d664d4d31894c528f092083b5cfe0e79c51be2663e disp=flagged flag="sanitizer_stripped" attempts=1 final_hash=tm-sanitized-v1:6f0567f25e056974a065d6ea5aedc9b204eeaa7ae7648bd7f3bf34366348355e cost=0.00182 escalated=false esc_model="" detail="markdown-заголовок в выходе: ### Глава 7"
|
||||
ch8/0 draft snap_match=true content_hash=fbd27af11da2275cd1d47a0224970a5f8d073c6164f614efb6522ea96fa1e021 disp=ok flag="" attempts=1 final_hash=f5da217f1c2a26da0f7b7d946dada785e03cb30dc13600272cf74b0c54704451 cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch8/0 edit snap_match=true content_hash=0c6030a943cb341390509e2ebed4bbfd52af328d36916ce2e341f43665cf9183 disp=flagged flag="sanitizer_stripped" attempts=1 final_hash=tm-sanitized-v1:e0ea15352185a07b28f7b8b2ca50567d1b8044c5d8ad54ad6fd9d3e45f761c51 cost=0.00182 escalated=false esc_model="" detail="CJK-утечка в ru-выходе: 特产"
|
||||
ch7/0 draft snap_match=true content_hash=d90c335cba3d1547a22bb3f656443cb432f84c12a577d43509cfe472cf86a2ac disp=ok flag="" attempts=1 final_hash=3a1050fe790ac5eb004c8d3e80a05cc6ff5e6b46c5947f6a57a7145985591dfb cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch7/0 edit snap_match=true content_hash=e12521b6eb6dbc2cd35758d664d4d31894c528f092083b5cfe0e79c51be2663e disp=flagged flag="sanitizer_stripped" attempts=1 final_hash=tm-sanitized-v1:e9a837dbcd8d6717d27413735debe11bc116df2f65655d16f6fcbf51cbf59e66 cost=0.00182 escalated=false esc_model="" detail="markdown-заголовок в выходе: ### Глава 7"
|
||||
ch8/0 draft snap_match=true content_hash=fbd27af11da2275cd1d47a0224970a5f8d073c6164f614efb6522ea96fa1e021 disp=ok flag="" attempts=1 final_hash=e1e065fc34385f41fd51a4b0db42d5f391532775ef68ece015f261a33d6b68da cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch8/0 edit snap_match=true content_hash=0c6030a943cb341390509e2ebed4bbfd52af328d36916ce2e341f43665cf9183 disp=flagged flag="sanitizer_stripped" attempts=1 final_hash=tm-sanitized-v1:08f107da1257ff083f73e9af1e8d3b5b90e699ad6343faa80bebe891744c7adf cost=0.00182 escalated=false esc_model="" detail="CJK-утечка в ru-выходе: 特产"
|
||||
-- request_log (insertion order) --
|
||||
ch1/0 draft role=translator req=fake-model actual=fake-model hash=96f55c8caeec48506a73141758fe8e819c9da32c9f6d969e02b83057cc2a7af3 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=c9b9807b0279cb186cfae7e1255b2ea6e608b994b3429df9e7cb48f40c21b520 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=2e498c5ae088fcd6701b94c2f2ad7761033b6f663a26a48554ae12ca2c2f2630 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=999ab8cd87c3505efaade5cc30b7f2d38a4e3fc6183250f34906e5c2db84f078 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=6b744aad02e6e2d255353035a3943a278bc407cf2b1f2abaac9503c0d21239da 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=c42d577ea9c432be30ea7716f15b779320113de3d1269559988fde390fe1589d 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=ac14e02f697c4fc4963946365a118afb5e8ba974c0d3c6781aac17d403dec5fd 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=7a7a6a7c55f5dcd00a80d04e8da5b4bc0f4b9bca8a33baa24397b7dfa9424e20 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=a96545d035e16b72a6abce2274f2ea4664399da63b9aaf88e5dfb3a96af20e2d 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=9af233395ddde718f61b1bbb4709095a43ea3c4e146ca10990e821607b92bca5 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=f5da217f1c2a26da0f7b7d946dada785e03cb30dc13600272cf74b0c54704451 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=dd9978846ec37ddde3e4db428582661271fd51e8cccbce1374fd2200800e9614 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=894afeaecfc7bef655d937d01a1c69b920c9500d10e4de9d1095f4391a55adac 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=5db400bde72cc0e7991f5860a524a73db4b511c683ef918cad505ffba3a6e314 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=3fdb393a5e9bcba9869c7e0125d89e07f8bf64bb33c0098d999a9dcf5f5285ae 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=8db26b560abdf5e538830d6e60d6552225140163668ee698936990ceba379189 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=9ba86bea5f00ed45d089fea42c4353ed4321ade3980514765e26cf891aedd319 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=943591a7303b83478ab8f24af256c4e663b7b01f9fed670a704ae10a330798f8 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=365abc01adb22d047a3865c79231c2669f7db960fd07069d46547d562cfc537b 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=6f883d099cd0924eab5db1ef3eb730aa50429e719ffeea95df93e33042e9fcc0 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=0932d7aee2cd8fe944a4f5fcc4bf5b1828d8514ecc46cde7b5a3bd9983864dd2 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=f7a858a11f0890da139789d27bea23b1c33c83ef989f2d07dc51d4b238d5e16e 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=7be9635f28275d919b1ff3ff6aab9f94039eff250163bef3d6d3460489984b7e 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=ef672cdf6862522d15d93c32faf95d7eebc2c6ff4c24de6b0679a256900349ac 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=6fb9e932865b46557a1f5e4a0852a6fe5b2ed728e54d5704801840f1dbe89ccb 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=03ed6d252620e8763a0afc2d535e1b6e1cf97ca1145a66aa08ea199ef7102c04 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=1efa0b7620e14fa8cdff89a198efe113e2196a4a48717b1dfacfc9bc878d2ae8 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=3a1050fe790ac5eb004c8d3e80a05cc6ff5e6b46c5947f6a57a7145985591dfb 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=e1e065fc34385f41fd51a4b0db42d5f391532775ef68ece015f261a33d6b68da 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=42e10469327dcf7d8409cda457a7eff626795a709ff0b37f3de260d7ca11986a 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=ff45d79e2d08b57328491e1ac5e0b6b67ba883346c44806b7a63e7adddcf65bf 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=5b1f425d363f501da5832d570ebeb036a8d780378abf3e92cdded87c247b9b6e 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=3367114609df41762ff6f64b57de90f85b6f1992987ebf875bc64e028a6a0a4b 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=db25b5d375286a9fa18bc3b7b9da14c8bd95a5209b8b2e9099889413062504d1 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=54ab4a1ffb853422a3b079b71b3c1c7ba11eb434c3d9f1df3c81aec2f007e0d8 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=c047ff047484181ff697d1e525c3d12f5b76ab5f9451cc5f309be3465559cf5e 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,13 +126,13 @@ 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":"Черновик перевода для редактуры: ЧЕРНОВИК ПЕРЕВОДА ec12f3d7a1df. МАРКДАУН-СЦЕНА Судзуки открыл седьмую дверь."}],"model":"fake-model","stream":false,"temperature":0.4}
|
||||
[17] {"max_tokens":4000,"messages":[{"role":"system","content":"Ты — монолингвальный редактор русского текста. Правь стиль, не меняя смысла. Книга: Золотая книга."},{"role":"system","content":"КАНОНИЧЕСКИЕ ПЕРЕВОДЫ имён и терминов (в черновике термин исходника слева ДОЛЖЕН быть передан именно указанной формой справа — приводи к ней любые расхождения, склоняя по контексту; не вводи иных вариантов и не меняй ничего другого):\n- 鈴木 → «Судзуки» (муж. — мужские родовые формы)"},{"role":"user","content":"Черновик перевода для редактуры: ЧЕРНОВИК ПЕРЕВОДА 0bc6a4817acb. ИЕРОГЛИФ-СЦЕНА Судзуки нашёл камень."}],"model":"fake-model","stream":false,"temperature":0.4}
|
||||
==== run 2 (resume) ====
|
||||
snapshot_draft: 914c9434d646ec173dbafbac5f2355d9bd4920bf9e3499f237a97c046d82c84d
|
||||
snapshot_draft_payload: {"brief_hash":"c2021b1b5e6c20f6084e425b9a70bdc5f682801be347a57eb1c591deb8cab0df","chunker_version":"chunker-v5-output-budget-editunit+u15.0.0","estimator_version":"estimator-v0+u15.0.0","max_tokens_policy":"maxtok-v1-double-per-attempt","classifier_version":"classify-v1-refusal+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-v2-editor-src2dst+dc3-gender","memory_version":"173e87fc011e832c108711097eeffc05db95ab56eafe2ef913b43829505cdfdd","postcheck_gate":false,"coverage":{"enabled":false},"style_check_version":"cheapgate-v3-dc-checkers+u15.0.0","sanitizer":{"enabled":true,"version":"sanitizer-v6"},"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: 6fe7e9463b2bb3a93c602468201d650ffcc489c02042099351ac8e66be6f17ed
|
||||
snapshot_edit_payload: {"brief_hash":"c2021b1b5e6c20f6084e425b9a70bdc5f682801be347a57eb1c591deb8cab0df","chunker_version":"chunker-v5-output-budget-editunit+u15.0.0","estimator_version":"estimator-v0+u15.0.0","max_tokens_policy":"maxtok-v1-double-per-attempt","classifier_version":"classify-v1-refusal+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-v2-editor-src2dst+dc3-gender","memory_version":"740aefe880a34396172775751d41d0e9ef136d053f3200a4f94391fa72aee7e4","postcheck_gate":false,"coverage":{"enabled":false},"style_check_version":"cheapgate-v3-dc-checkers+u15.0.0","sanitizer":{"enabled":true,"version":"sanitizer-v6"},"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: b1279edc077f8f26c67e4e94c01eb29e02e0f706d4d18f267519bbb58f2e474a
|
||||
snapshot_draft_payload: {"brief_hash":"c2021b1b5e6c20f6084e425b9a70bdc5f682801be347a57eb1c591deb8cab0df","chunker_version":"chunker-v5-output-budget-editunit+u15.0.0","estimator_version":"estimator-v0+u15.0.0","max_tokens_policy":"maxtok-v1-double-per-attempt","classifier_version":"classify-v1-refusal+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-v2-editor-src2dst+dc3-gender","memory_version":"bd451833d4ebb8a0ca07caf8deda76f027e1f42b78654987a22d215acf955eb7","postcheck_gate":false,"coverage":{"enabled":false},"style_check_version":"cheapgate-v3-dc-checkers+u15.0.0","sanitizer":{"enabled":true,"version":"sanitizer-v6"},"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: 66a990322f96d7f8f6d4783c63dbee75833b532e31d6a95d2de81394880d0a41
|
||||
snapshot_edit_payload: {"brief_hash":"c2021b1b5e6c20f6084e425b9a70bdc5f682801be347a57eb1c591deb8cab0df","chunker_version":"chunker-v5-output-budget-editunit+u15.0.0","estimator_version":"estimator-v0+u15.0.0","max_tokens_policy":"maxtok-v1-double-per-attempt","classifier_version":"classify-v1-refusal+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-v2-editor-src2dst+dc3-gender","memory_version":"0a48d6ed9409de09f514af91aebdd622637621c9dc9464e8898c238791033d91","postcheck_gate":false,"coverage":{"enabled":false},"style_check_version":"cheapgate-v3-dc-checkers+u15.0.0","sanitizer":{"enabled":true,"version":"sanitizer-v6"},"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: 740aefe880a34396172775751d41d0e9ef136d053f3200a4f94391fa72aee7e4
|
||||
base_memory_version: 173e87fc011e832c108711097eeffc05db95ab56eafe2ef913b43829505cdfdd
|
||||
memory_version: 0a48d6ed9409de09f514af91aebdd622637621c9dc9464e8898c238791033d91
|
||||
base_memory_version: bd451833d4ebb8a0ca07caf8deda76f027e1f42b78654987a22d215acf955eb7
|
||||
-- book result --
|
||||
chunks=8 flagged=4 exit=2 total_usd=0
|
||||
chunk ch1/0 disposition=ok flag="" final_text="ОТРЕДАКТИРОВАННЫЙ ПЕРЕВОД 22c30f20dea6. Судзуки шёл по коридорам Академии магии." cost=0
|
||||
|
|
@ -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-утечка в ru-выходе: 特产"
|
||||
stage_text="" recovered="Судзуки нашёл древний камень на каменном алтаре."
|
||||
-- chunk_status --
|
||||
ch1/0 draft snap_match=true content_hash=331c6f32c7f3ed15447e2639a2621d24a180e561a2aee92115324f492db5de3e disp=ok flag="" attempts=1 final_hash=96f55c8caeec48506a73141758fe8e819c9da32c9f6d969e02b83057cc2a7af3 cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch1/0 edit snap_match=true content_hash=8a4e763f79c94ad20a1bac0231a8c1376ccd4f5a71c53cdfcb4de146c0b48b42 disp=ok flag="" attempts=1 final_hash=dd9978846ec37ddde3e4db428582661271fd51e8cccbce1374fd2200800e9614 cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch1/1 draft snap_match=true content_hash=12fe3ccdf062f43e011363a33ff85c9b6842a8f23d790991fe9afab51a8becc8 disp=ok flag="" attempts=1 final_hash=c9b9807b0279cb186cfae7e1255b2ea6e608b994b3429df9e7cb48f40c21b520 cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch2/0 draft snap_match=true content_hash=2693325bc747687acf090bc263fe8c17783726b949c6d8a2d072da3401be6827 disp=ok flag="" attempts=1 final_hash=999ab8cd87c3505efaade5cc30b7f2d38a4e3fc6183250f34906e5c2db84f078 cost=0.00728 escalated=true esc_model="fake-fallback" detail=""
|
||||
ch2/0 edit snap_match=true content_hash=5a5397d41bcfa9326b5fe67c70aca8d9ef14ef89af2dbe1a758e25d364226fc5 disp=ok flag="" attempts=1 final_hash=894afeaecfc7bef655d937d01a1c69b920c9500d10e4de9d1095f4391a55adac cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch1/0 draft snap_match=true content_hash=331c6f32c7f3ed15447e2639a2621d24a180e561a2aee92115324f492db5de3e disp=ok flag="" attempts=1 final_hash=365abc01adb22d047a3865c79231c2669f7db960fd07069d46547d562cfc537b cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch1/0 edit snap_match=true content_hash=8a4e763f79c94ad20a1bac0231a8c1376ccd4f5a71c53cdfcb4de146c0b48b42 disp=ok flag="" attempts=1 final_hash=42e10469327dcf7d8409cda457a7eff626795a709ff0b37f3de260d7ca11986a cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch1/1 draft snap_match=true content_hash=12fe3ccdf062f43e011363a33ff85c9b6842a8f23d790991fe9afab51a8becc8 disp=ok flag="" attempts=1 final_hash=6f883d099cd0924eab5db1ef3eb730aa50429e719ffeea95df93e33042e9fcc0 cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch2/0 draft snap_match=true content_hash=2693325bc747687acf090bc263fe8c17783726b949c6d8a2d072da3401be6827 disp=ok flag="" attempts=1 final_hash=f7a858a11f0890da139789d27bea23b1c33c83ef989f2d07dc51d4b238d5e16e cost=0.00728 escalated=true esc_model="fake-fallback" detail=""
|
||||
ch2/0 edit snap_match=true content_hash=5a5397d41bcfa9326b5fe67c70aca8d9ef14ef89af2dbe1a758e25d364226fc5 disp=ok flag="" attempts=1 final_hash=ff45d79e2d08b57328491e1ac5e0b6b67ba883346c44806b7a63e7adddcf65bf cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch3/0 draft snap_match=true content_hash=733fc6a89215efe112cfadd1d1fb361bb8a7ab1013994248a49da0dbd137548e disp=flagged flag="hard_refusal" attempts=1 final_hash= cost=0.00728 escalated=true esc_model="fake-fallback" 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=ac14e02f697c4fc4963946365a118afb5e8ba974c0d3c6781aac17d403dec5fd cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch4/0 edit snap_match=true content_hash=2854dfe1648b0cf0cbb683cb7ce94c5eedc9223b2ce20c99dc008c7add6317ef disp=ok flag="" attempts=1 final_hash=5db400bde72cc0e7991f5860a524a73db4b511c683ef918cad505ffba3a6e314 cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch5/0 draft snap_match=true content_hash=d8aba42cabb41853a81875ebc725bb516dc2412e29de3a03bc1a68e90f8cf157 disp=ok flag="" attempts=1 final_hash=7a7a6a7c55f5dcd00a80d04e8da5b4bc0f4b9bca8a33baa24397b7dfa9424e20 cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch5/0 edit snap_match=true content_hash=82b05e4c3f5ca8432d7eaa7d7d63c4a7766f47a08398953c9a9bc3dc8437039b disp=ok flag="" attempts=1 final_hash=3fdb393a5e9bcba9869c7e0125d89e07f8bf64bb33c0098d999a9dcf5f5285ae cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch6/0 draft snap_match=true content_hash=9375b05780724cc354eba15a595da9a5f964219c0887042484256eed11fb566f disp=ok flag="" attempts=1 final_hash=a96545d035e16b72a6abce2274f2ea4664399da63b9aaf88e5dfb3a96af20e2d cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch4/0 draft snap_match=true content_hash=7aabdcb89dea365c95363b2662144ca1a10b307d377f47a8e7cc19b5b872d287 disp=ok flag="" attempts=1 final_hash=6fb9e932865b46557a1f5e4a0852a6fe5b2ed728e54d5704801840f1dbe89ccb cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch4/0 edit snap_match=true content_hash=2854dfe1648b0cf0cbb683cb7ce94c5eedc9223b2ce20c99dc008c7add6317ef disp=ok flag="" attempts=1 final_hash=5b1f425d363f501da5832d570ebeb036a8d780378abf3e92cdded87c247b9b6e cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch5/0 draft snap_match=true content_hash=d8aba42cabb41853a81875ebc725bb516dc2412e29de3a03bc1a68e90f8cf157 disp=ok flag="" attempts=1 final_hash=03ed6d252620e8763a0afc2d535e1b6e1cf97ca1145a66aa08ea199ef7102c04 cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch5/0 edit snap_match=true content_hash=82b05e4c3f5ca8432d7eaa7d7d63c4a7766f47a08398953c9a9bc3dc8437039b disp=ok flag="" attempts=1 final_hash=3367114609df41762ff6f64b57de90f85b6f1992987ebf875bc64e028a6a0a4b cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch6/0 draft snap_match=true content_hash=9375b05780724cc354eba15a595da9a5f964219c0887042484256eed11fb566f disp=ok flag="" attempts=1 final_hash=1efa0b7620e14fa8cdff89a198efe113e2196a4a48717b1dfacfc9bc878d2ae8 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="ведущая служебная преамбула: Вот перевод фрагмента:"
|
||||
ch7/0 draft snap_match=true content_hash=d90c335cba3d1547a22bb3f656443cb432f84c12a577d43509cfe472cf86a2ac disp=ok flag="" attempts=1 final_hash=9af233395ddde718f61b1bbb4709095a43ea3c4e146ca10990e821607b92bca5 cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch7/0 edit snap_match=true content_hash=e12521b6eb6dbc2cd35758d664d4d31894c528f092083b5cfe0e79c51be2663e disp=flagged flag="sanitizer_stripped" attempts=1 final_hash=tm-sanitized-v1:6f0567f25e056974a065d6ea5aedc9b204eeaa7ae7648bd7f3bf34366348355e cost=0.00182 escalated=false esc_model="" detail="markdown-заголовок в выходе: ### Глава 7"
|
||||
ch8/0 draft snap_match=true content_hash=fbd27af11da2275cd1d47a0224970a5f8d073c6164f614efb6522ea96fa1e021 disp=ok flag="" attempts=1 final_hash=f5da217f1c2a26da0f7b7d946dada785e03cb30dc13600272cf74b0c54704451 cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch8/0 edit snap_match=true content_hash=0c6030a943cb341390509e2ebed4bbfd52af328d36916ce2e341f43665cf9183 disp=flagged flag="sanitizer_stripped" attempts=1 final_hash=tm-sanitized-v1:e0ea15352185a07b28f7b8b2ca50567d1b8044c5d8ad54ad6fd9d3e45f761c51 cost=0.00182 escalated=false esc_model="" detail="CJK-утечка в ru-выходе: 特产"
|
||||
ch7/0 draft snap_match=true content_hash=d90c335cba3d1547a22bb3f656443cb432f84c12a577d43509cfe472cf86a2ac disp=ok flag="" attempts=1 final_hash=3a1050fe790ac5eb004c8d3e80a05cc6ff5e6b46c5947f6a57a7145985591dfb cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch7/0 edit snap_match=true content_hash=e12521b6eb6dbc2cd35758d664d4d31894c528f092083b5cfe0e79c51be2663e disp=flagged flag="sanitizer_stripped" attempts=1 final_hash=tm-sanitized-v1:e9a837dbcd8d6717d27413735debe11bc116df2f65655d16f6fcbf51cbf59e66 cost=0.00182 escalated=false esc_model="" detail="markdown-заголовок в выходе: ### Глава 7"
|
||||
ch8/0 draft snap_match=true content_hash=fbd27af11da2275cd1d47a0224970a5f8d073c6164f614efb6522ea96fa1e021 disp=ok flag="" attempts=1 final_hash=e1e065fc34385f41fd51a4b0db42d5f391532775ef68ece015f261a33d6b68da cost=0.00182 escalated=false esc_model="" detail=""
|
||||
ch8/0 edit snap_match=true content_hash=0c6030a943cb341390509e2ebed4bbfd52af328d36916ce2e341f43665cf9183 disp=flagged flag="sanitizer_stripped" attempts=1 final_hash=tm-sanitized-v1:08f107da1257ff083f73e9af1e8d3b5b90e699ad6343faa80bebe891744c7adf cost=0.00182 escalated=false esc_model="" detail="CJK-утечка в ru-выходе: 特产"
|
||||
-- request_log (insertion order) --
|
||||
ch1/0 draft role=translator req=fake-model actual=fake-model hash=96f55c8caeec48506a73141758fe8e819c9da32c9f6d969e02b83057cc2a7af3 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=c9b9807b0279cb186cfae7e1255b2ea6e608b994b3429df9e7cb48f40c21b520 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=2e498c5ae088fcd6701b94c2f2ad7761033b6f663a26a48554ae12ca2c2f2630 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=999ab8cd87c3505efaade5cc30b7f2d38a4e3fc6183250f34906e5c2db84f078 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=6b744aad02e6e2d255353035a3943a278bc407cf2b1f2abaac9503c0d21239da 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=c42d577ea9c432be30ea7716f15b779320113de3d1269559988fde390fe1589d 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=ac14e02f697c4fc4963946365a118afb5e8ba974c0d3c6781aac17d403dec5fd 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=7a7a6a7c55f5dcd00a80d04e8da5b4bc0f4b9bca8a33baa24397b7dfa9424e20 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=a96545d035e16b72a6abce2274f2ea4664399da63b9aaf88e5dfb3a96af20e2d 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=9af233395ddde718f61b1bbb4709095a43ea3c4e146ca10990e821607b92bca5 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=f5da217f1c2a26da0f7b7d946dada785e03cb30dc13600272cf74b0c54704451 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=dd9978846ec37ddde3e4db428582661271fd51e8cccbce1374fd2200800e9614 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=894afeaecfc7bef655d937d01a1c69b920c9500d10e4de9d1095f4391a55adac 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=5db400bde72cc0e7991f5860a524a73db4b511c683ef918cad505ffba3a6e314 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=3fdb393a5e9bcba9869c7e0125d89e07f8bf64bb33c0098d999a9dcf5f5285ae 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=8db26b560abdf5e538830d6e60d6552225140163668ee698936990ceba379189 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=9ba86bea5f00ed45d089fea42c4353ed4321ade3980514765e26cf891aedd319 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=943591a7303b83478ab8f24af256c4e663b7b01f9fed670a704ae10a330798f8 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=96f55c8caeec48506a73141758fe8e819c9da32c9f6d969e02b83057cc2a7af3 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=c9b9807b0279cb186cfae7e1255b2ea6e608b994b3429df9e7cb48f40c21b520 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=999ab8cd87c3505efaade5cc30b7f2d38a4e3fc6183250f34906e5c2db84f078 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=365abc01adb22d047a3865c79231c2669f7db960fd07069d46547d562cfc537b 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=6f883d099cd0924eab5db1ef3eb730aa50429e719ffeea95df93e33042e9fcc0 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=0932d7aee2cd8fe944a4f5fcc4bf5b1828d8514ecc46cde7b5a3bd9983864dd2 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=f7a858a11f0890da139789d27bea23b1c33c83ef989f2d07dc51d4b238d5e16e 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=7be9635f28275d919b1ff3ff6aab9f94039eff250163bef3d6d3460489984b7e 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=ef672cdf6862522d15d93c32faf95d7eebc2c6ff4c24de6b0679a256900349ac 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=6fb9e932865b46557a1f5e4a0852a6fe5b2ed728e54d5704801840f1dbe89ccb 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=03ed6d252620e8763a0afc2d535e1b6e1cf97ca1145a66aa08ea199ef7102c04 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=1efa0b7620e14fa8cdff89a198efe113e2196a4a48717b1dfacfc9bc878d2ae8 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=3a1050fe790ac5eb004c8d3e80a05cc6ff5e6b46c5947f6a57a7145985591dfb 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=e1e065fc34385f41fd51a4b0db42d5f391532775ef68ece015f261a33d6b68da 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=42e10469327dcf7d8409cda457a7eff626795a709ff0b37f3de260d7ca11986a 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=ff45d79e2d08b57328491e1ac5e0b6b67ba883346c44806b7a63e7adddcf65bf 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=5b1f425d363f501da5832d570ebeb036a8d780378abf3e92cdded87c247b9b6e 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=3367114609df41762ff6f64b57de90f85b6f1992987ebf875bc64e028a6a0a4b 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=db25b5d375286a9fa18bc3b7b9da14c8bd95a5209b8b2e9099889413062504d1 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=54ab4a1ffb853422a3b079b71b3c1c7ba11eb434c3d9f1df3c81aec2f007e0d8 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=c047ff047484181ff697d1e525c3d12f5b76ab5f9451cc5f309be3465559cf5e 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=365abc01adb22d047a3865c79231c2669f7db960fd07069d46547d562cfc537b 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=6f883d099cd0924eab5db1ef3eb730aa50429e719ffeea95df93e33042e9fcc0 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=f7a858a11f0890da139789d27bea23b1c33c83ef989f2d07dc51d4b238d5e16e 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=ac14e02f697c4fc4963946365a118afb5e8ba974c0d3c6781aac17d403dec5fd 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=7a7a6a7c55f5dcd00a80d04e8da5b4bc0f4b9bca8a33baa24397b7dfa9424e20 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=a96545d035e16b72a6abce2274f2ea4664399da63b9aaf88e5dfb3a96af20e2d 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=9af233395ddde718f61b1bbb4709095a43ea3c4e146ca10990e821607b92bca5 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=f5da217f1c2a26da0f7b7d946dada785e03cb30dc13600272cf74b0c54704451 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=dd9978846ec37ddde3e4db428582661271fd51e8cccbce1374fd2200800e9614 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=894afeaecfc7bef655d937d01a1c69b920c9500d10e4de9d1095f4391a55adac 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=5db400bde72cc0e7991f5860a524a73db4b511c683ef918cad505ffba3a6e314 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=3fdb393a5e9bcba9869c7e0125d89e07f8bf64bb33c0098d999a9dcf5f5285ae 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=6fb9e932865b46557a1f5e4a0852a6fe5b2ed728e54d5704801840f1dbe89ccb 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=03ed6d252620e8763a0afc2d535e1b6e1cf97ca1145a66aa08ea199ef7102c04 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=1efa0b7620e14fa8cdff89a198efe113e2196a4a48717b1dfacfc9bc878d2ae8 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=3a1050fe790ac5eb004c8d3e80a05cc6ff5e6b46c5947f6a57a7145985591dfb 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=e1e065fc34385f41fd51a4b0db42d5f391532775ef68ece015f261a33d6b68da 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=42e10469327dcf7d8409cda457a7eff626795a709ff0b37f3de260d7ca11986a 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=ff45d79e2d08b57328491e1ac5e0b6b67ba883346c44806b7a63e7adddcf65bf 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=5b1f425d363f501da5832d570ebeb036a8d780378abf3e92cdded87c247b9b6e 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=3367114609df41762ff6f64b57de90f85b6f1992987ebf875bc64e028a6a0a4b 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:6f0567f25e056974a065d6ea5aedc9b204eeaa7ae7648bd7f3bf34366348355e 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:e0ea15352185a07b28f7b8b2ca50567d1b8044c5d8ad54ad6fd9d3e45f761c51 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:e9a837dbcd8d6717d27413735debe11bc116df2f65655d16f6fcbf51cbf59e66 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:08f107da1257ff083f73e9af1e8d3b5b90e699ad6343faa80bebe891744c7adf 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=
|
||||
|
|
|
|||
|
|
@ -69,12 +69,15 @@ func (r *Runner) outputUnits(chunks []Chunk) []editUnit {
|
|||
return out
|
||||
}
|
||||
|
||||
// WaveSignatureStop is the typed sentinel the CLI surfaces when bank-mining proposes a non-empty seed-delta:
|
||||
// the run completes the draft wave, writes the owner signature map, and STOPS before the edit wave (plan §1(в) default — an explicit
|
||||
// stop boundary, not a mid-run append). The owner reviews the signature map, curates the mined-delta file
|
||||
// (promoting terms to approved with a dst), and re-runs: the draft wave resumes at $0, the bank-mining stop re-mines (the now-seeded
|
||||
// terms are excluded → the delta empties → auto-continue), and the edit wave runs under edit-wave snapshot. NOT an infra
|
||||
// failure — a deliberate human-in-the-loop pause, carried up like CompletedWithFlags.
|
||||
// WaveSignatureStop is the typed sentinel the driver returns when bank-mining proposes a non-empty
|
||||
// seed-delta: the run completes the draft wave, writes the owner signature map, and STOPS before the edit
|
||||
// wave (plan §1(в) default — an explicit stop boundary, not a mid-run append). The owner reviews the
|
||||
// signature map, then either promotes a term into the mined-delta file (approved + dst) or declines it in
|
||||
// the mined-rejects file, and re-runs: the draft wave resumes at $0, the bank-mining stop re-mines (the
|
||||
// now-seeded AND rejected terms are excluded → the delta empties → auto-continue), and the edit wave runs
|
||||
// under the edit-wave snapshot. NOT an infra failure — a deliberate human-in-the-loop pause. The CLI maps
|
||||
// it (errors.As) to a DISTINCT exit code (3, unlike CompletedWithFlags's 2 or a crash's 1) and renders the
|
||||
// terms + signature-map path to the operator (main.go/renderSignatureStop, R1-FL-A).
|
||||
type WaveSignatureStop struct {
|
||||
Terms int
|
||||
SignaturePath string
|
||||
|
|
|
|||
|
|
@ -99,8 +99,8 @@ func TestWaveMultiChunkEditUnit(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if exp.TotalChunks != 1 || exp.PendingChunks != 0 || len(exp.Chunks) != 1 {
|
||||
t.Fatalf("export must be 1 unit / 0 pending, got total=%d pending=%d rows=%d", exp.TotalChunks, exp.PendingChunks, len(exp.Chunks))
|
||||
if exp.TotalUnits != 1 || exp.PendingUnits != 0 || len(exp.Chunks) != 1 {
|
||||
t.Fatalf("export must be 1 unit / 0 pending, got total=%d pending=%d rows=%d", exp.TotalUnits, exp.PendingUnits, len(exp.Chunks))
|
||||
}
|
||||
if exp.Chunks[0].FinalText != "ОТРЕДАКТИРОВАННЫЙ ПЕРЕВОД" {
|
||||
t.Fatalf("export unit text mismatch: %q", exp.Chunks[0].FinalText)
|
||||
|
|
@ -115,8 +115,8 @@ func TestWaveMultiChunkEditUnit(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if st.TotalChunks != 1 || st.Done != 1 {
|
||||
t.Fatalf("status must be 1 unit done, got total=%d done=%d", st.TotalChunks, st.Done)
|
||||
if st.TotalUnits != 1 || st.Done != 1 {
|
||||
t.Fatalf("status must be 1 unit done, got total=%d done=%d", st.TotalUnits, st.Done)
|
||||
}
|
||||
r2.Close()
|
||||
|
||||
|
|
@ -156,6 +156,11 @@ func TestRunWaveSurfacesParentCancellation(t *testing.T) {
|
|||
// many chunks and asserts the money invariant survives concurrency: committed spend == the sum of the
|
||||
// settled checkpoints (the single-writer store serializes Reserve/Settle), and the per-unit result is
|
||||
// complete + deterministic. Run under `-race` this also proves the waves share only read-only state.
|
||||
//
|
||||
// It also exercises the REAL internal/llm HTTP adapter under concurrency (D39.17-errata nit): the 4 workers
|
||||
// fire concurrent requests through the real transport at an httptest.Server (setupProjectOpts writes a
|
||||
// models.yaml pointing at the live URL — not a fake in-process client), so a shared-client data race would
|
||||
// trip -race here. stdlib http.Client is safe for concurrent use by design; this pins it end-to-end.
|
||||
func TestWaveParallelWorkersMoneyConserved(t *testing.T) {
|
||||
rec := &reqRec{}
|
||||
srv := newJSONProvider(rec, draftEdit)
|
||||
|
|
@ -267,14 +272,19 @@ func TestWaveEditUnitFlaggedMemberDraft(t *testing.T) {
|
|||
if exp.Chunks[0].FinalText != "ОТРЕДАКТИРОВАННЫЙ ПЕРЕВОД" {
|
||||
t.Fatalf("export must ship the edited clean remainder for a dropped-member unit, got %q", exp.Chunks[0].FinalText)
|
||||
}
|
||||
// The dropped member's echo must NOT vanish from the quality telemetry the owner reads for acceptance
|
||||
// (c-lite makes the leader edit row DispOK; quality re-derives the drop like export/status).
|
||||
// Echo split (D39.18): the dropped member's echo is a TRANSLATOR echo → it counts in echo_draft
|
||||
// (read DIRECTLY off the draft row, no c-lite re-derivation). The editor recovered the unit over the
|
||||
// clean remainder, so its own output did NOT echo → echo_edit is 0. This is the load-bearing case the
|
||||
// split fixes: the draft echo stays visible while the delivered (edit) quality is honestly clean.
|
||||
q, err := r2.QualityReport()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if q.EchoChunks != 1 {
|
||||
t.Fatalf("the dropped member's echo must still count in QualityReport (EchoRate not silently regressed), got EchoChunks=%d", q.EchoChunks)
|
||||
if q.EchoDraftChunks != 1 || q.EchoDraftRate != 0.5 { // 1 of 2 draft chunks echoed
|
||||
t.Fatalf("the dropped member's echo must count in echo_draft, got EchoDraftChunks=%d rate=%.3f (want 1 / 0.5)", q.EchoDraftChunks, q.EchoDraftRate)
|
||||
}
|
||||
if q.EchoEditUnits != 0 || q.EchoEditRate != 0 {
|
||||
t.Fatalf("the editor recovered the clean remainder → echo_edit must be 0, got EchoEditUnits=%d rate=%.3f", q.EchoEditUnits, q.EchoEditRate)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@
|
|||
> - **Ждём от владельца:** тачпойнты exp16 (карта подписи/пол · мини-голд алиасов · precision@30, `books/gu-zhenren/exp16/`, ~30–40 мин — для сид-дельты к пере-прогону) · развилки плана §10 (W1.5-UX · DC5-стих-политика · Edit-ceiling · и др.) · реплика ja→ru (тест общности §B5) · **ре-чек прайса DeepSeek у катовера слагов 24.07** (до него платных deepseek-прогонов нет) · чтение пере-прогона (после R1+resnapshot) · старые висящие: планка запуска (лучше-фана/гибрид/издательский) · билингв-якорь пилота (D25.9-Q1) · контаминация пилот-корпуса (D27.4) · publishable/waiver (D25.1) · FN-bound L3 (D25.4) · юр-пакет · провенанс 12-*-доков.
|
||||
> - Архивы хроники: `archive/PROGRESS-2026-07-04-10.md` (D31) · `archive/PROGRESS-2026-07-10-13.md` (D39.6-гигиена). Записи ниже — живая эра D39.
|
||||
|
||||
## Оркестратор №7 — пере-прогон-преп ПРИНЯТ и залендён (D39.19), бэкенд готов к пере-прогону, 19.07
|
||||
|
||||
Сессия исполнила все 6 фиксов препа + §8-верификацию + свой 4-линзовый адверсариал (clean bill). **Залендено:** R1-FL-A (CLI exit-3 для WaveSignatureStop) · R1-FL-B (reject-set `Book.MinedRejects` — livelock/W1.5-UX закрыт; nil no-op, парити цел) · R1-FL-C (x/text-версия в Ш-2, pin+drift-чек — единственная снапшот-двигающая) · echo-сплит (draft/edit rates, костыль снят) · read-model naming (chunk→unit, полигон не сломан) · ниты (мёртвый minedToCandidates снесён). **Приёмка прямая (локальные фиксы, сессия сама адверсариалила):** build/race зелёные сам, **независимый masked-golden = 0 после маскировки хешей** (version-only чисто, wire/вердикты байт-идентичны), пины все (exit-контракт, reject-suppress, x/text-drift, echo-сплит, парити EXACT), §8-манифест полон (+x/text). Док-правка: x/text-строка добавлена в план §8 (моя зона). **ПАК-11+преп завершены, бэкенд готов.** Промт препа отработан→архив. **Очередь: пере-прогон** (операционная: ОДИН resnapshot + langpack+contrast+подписанная карта+армы; риг D39.7; finish=stop) → чтение (планка 2 претензии). Гейт: ре-чек прайса DeepSeek 24.07.
|
||||
|
||||
## Оркестратор №7 — gender-signoff закрыт + промт пере-прогон-препа выдан, 19.07
|
||||
|
||||
**Gender-signoff карты exp16 ЗАКРЫТ** (владелец «male ок», оркестратор проставил корректно, не бланк-штампом): **male** (name-персоны he-доминантно) 方源·古月·方正·人祖·花酒行者·古月赤练/赤城/漠北/漠尘/冻土 · **female** 沈嬷嬷 (嬷嬷=нянька, 0/2) + **沈翠** (KWIC разрешил ничью 2/2: 丫鬟/丫头=служанка, дочь 沈嬷嬷 — счётчики 他 были от окружающих мужчин) · **hidden** 白凝冰 · **n/a** все title/term/place + 困境 («Беды»=имя стаи). Флагов не осталось. Карта (`books/gu-zhenren/exp16/signature_map.md`, вне git) — dst-канон + пол закрыты, готова к сид-дельте. **Промт `BACKEND_RERUN_PREP_SESSION_PROMPT.md` ВЫДАН** — 6 целевых фиксов (R1-FL-A CLI-exit · R1-FL-B mining-reject-set [дизайн-дефолт: reject-list файлом] · R1-FL-C x/text-версия в Ш-2 · echo-сплит · read-model naming · лёгкие ниты) + верификация полноты §8-манифеста единого resnapshot; директива «не упарываться на миноры» вшита. Пере-прогон (ОДИН resnapshot fresh-translate + армы + подписанная карта) — операционная сессия ПОСЛЕ препа; гейт: ре-чек прайса DeepSeek 24.07.
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
- `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).
|
||||
- `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)** — у всех ревью-шапки. ⚠ Часть под superseded-баннерами (01/02/03/04/05/09 и gap-1/2/5) — **читай баннер прежде содержимого**.
|
||||
- `PROGRESS.md` — **журнал** (CURRENT-STATE сверху, ниже хронология; НЕ источник решений).
|
||||
- **Активные хендофф-промты (пост-D39.18, 19.07):** [`BACKEND_RERUN_PREP_SESSION_PROMPT.md`](BACKEND_RERUN_PREP_SESSION_PROMPT.md) (**СТАРТОВЫЙ — пере-прогон-преп: 6 целевых фиксов [R1-FL-A/B/C · echo-сплит · read-model naming · ниты] + готовность §8-манифеста; перед пере-прогоном**) · [`ORCHESTRATOR_SESSION_PROMPT.md`](ORCHESTRATOR_SESSION_PROMPT.md) (хендофф №5 оркестратора) · [`POLYGON_PACKAGE4_SESSION_PROMPT.md`](POLYGON_PACKAGE4_SESSION_PROMPT.md) (residual пилот/18+/echo). **ПАК-11 ЗАВЕРШЁН.** Очередь: преп → пере-прогон (ОДИН resnapshot + армы + подписанная карта) → чтение. Закрытые — в `archive/prompts/` (свежие: пак-11/R1→D39.17, арх-cleanup→D39.16).
|
||||
- **Активные хендофф-промты (пост-D39.19, 19.07):** [`ORCHESTRATOR_SESSION_PROMPT.md`](ORCHESTRATOR_SESSION_PROMPT.md) (хендофф №5 оркестратора) · [`POLYGON_PACKAGE4_SESSION_PROMPT.md`](POLYGON_PACKAGE4_SESSION_PROMPT.md) (residual пилот/18+/echo). **ПАК-11 + ПРЕП ЗАВЕРШЕНЫ, бэкенд готов к пере-прогону.** Следующий шаг = **пере-прогон** (операционная сессия: ОДИН `--resnapshot` fresh-translate + langpack+contrast+подписанная карта exp16 + армы редактора; промт пишет оркестратор). Гейт: ре-чек прайса DeepSeek 24.07. Закрытые — в `archive/prompts/` (свежие: преп→D39.19, пак-11/R1→D39.17, арх-cleanup→D39.16).
|
||||
- `archive/` — закрытые сессионные промты (только история, инструкции оттуда не исполнять).
|
||||
|
||||
## Статус (2026-07-19, пост-D39.16 — стройка пере-прогонного стека)
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -827,6 +827,9 @@ WS5-omission-бэкстоп компенсирует WS2-зону деграда
|
|||
`memoryMatchVersion`; §5(в)/§2(в)); DC-чекеры → `StyleCheckVersion`; promote 甲乙丙丁/四代族长 → `MemoryVersion`.
|
||||
- WS6: editor-model per-арм → `stageSnap.Model` (разные армы = разные снапшоты, но это РАЗНЫЕ прогоны, не переоплата
|
||||
одного).
|
||||
- **R1-FL-C (D39.17/D39.19):** `xTextVersion` (`x/text/norm` Unicode-редакция, независима от stdlib `unicode.Version`)
|
||||
→ `memoryNormAlgoVersion` → `memoryNormVersion` → `MemoryVersion` (обе волны) → `snapshot_draft`+`snapshot_edit`.
|
||||
Иначе `go get -u golang.org/x/text` тихо сдвинул бы NFKC. Pin-константа, drift-чек против go.mod.
|
||||
- **Структурное (ревью-2 F9):** сама пер-волновая реструктуризация `snapshotID()` (два `MemoryVersion` base/enriched
|
||||
+ два снапшота W1/W2 вместо одного book-global, §1(в)) = снапшот-инвалидация — ОЖИДАЕМАЯ строка golden re-capture
|
||||
(структура хеша меняется даже при идентичном контенте).
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
> **⟶ ОТРАБОТАН → все 6 фиксов залендены, ратифицирован D39.19 (19.07). golden version-only, парити EXACT, §8-манифест полон. Бэкенд готов к пере-прогону. Архивная копия.**
|
||||
|
||||
# Промт: сессия БЭКЕНД — пере-прогон-преп (mining-wiring фиксы · echo-сплит · read-model чистка · готовность к единому resnapshot), 2026-07-19
|
||||
|
||||
> **Место в очереди (пост-D39.18):** ПАК-11 ЗАВЕРШЁН (Block A → continuation → арх-проход → R1 драйвер-свитч →
|
||||
Loading…
Add table
Reference in a new issue