1310 lines
60 KiB
JSON
1310 lines
60 KiB
JSON
[
|
||
{
|
||
"id": "A-lock-arbiter",
|
||
"why": "the lock bank-apply takes is the same flock a RUN holds; a pin that holds it with LockProject only proves LockProject conflicts with itself",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/store/store.go",
|
||
"find": "\tf, err := acquireLock(dbPath + \".lock\")",
|
||
"replace": "\tf, err := acquireLock(dbPath + \".lock-not-the-run-lock\")"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "B-report-mitigations",
|
||
"why": "canonical_rewrite and preexisting_problems are the two published fields the pack's own named risks are mitigated by",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/bankdecisions.go",
|
||
"find": "\trep.CanonicalRewrite = rep.CanonicalRewriteDelta || rep.CanonicalRewriteRejects",
|
||
"replace": "\trep.CanonicalRewrite = false"
|
||
},
|
||
{
|
||
"file": "internal/pipeline/bankdecisions.go",
|
||
"find": "\t\tPreexistingProblems: orEmpty(res.Preexisting),",
|
||
"replace": "\t\tPreexistingProblems: []string{},"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "C-refusal-table-total",
|
||
"why": "a refusal class with no exit number falls into the catch-all 19; the totality gate must enumerate the classes from the SOURCE, constants included",
|
||
"package": "./cmd/tmctl/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/refusal.go",
|
||
"find": "\tRefusalDecisionsRejected RefusalClass = \"decisions_rejected\"",
|
||
"replace": "\tRefusalDecisionsRejected RefusalClass = \"decisions_rejected\"\n\n\t// RefusalPlanted is a mutation: a class with no number in refusalExit.\n\tRefusalPlanted RefusalClass = \"planted_class\""
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "D-decline-normalizes",
|
||
"why": "the REPLACEMENT half of a decline must match the surface the way the matcher does — an approval written in the simplified form has to fall to a decline written in the traditional one",
|
||
"package": "./internal/membank/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/membank/decisions.go",
|
||
"find": "func dropTerms(f *seed.File, src string) []seed.Term {\n\tnk := text.NormalizeSourceKey(src)",
|
||
"replace": "func dropTerms(f *seed.File, src string) []seed.Term {\n\tnk := src"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "E1-ruby-deep-copy",
|
||
"why": "withRubyAliases must copy the alias slices deeply; AttachRubyAliasesToManual appends in place and would reach back into the seed rows the before/after comparison reads again",
|
||
"package": "./internal/membank/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/membank/decisions.go",
|
||
"find": "\t\te.Aliases = append([]store.GlossaryAlias(nil), e.Aliases...)",
|
||
"replace": "\t\t_ = e.Aliases"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "E2-promotion-note",
|
||
"why": "a promotion carries the owner's note into the delta row",
|
||
"package": "./internal/membank/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/membank/decisions.go",
|
||
"find": "\tif d.Note != \"\" {\n\t\tt.Note = d.Note\n\t}",
|
||
"replace": "\tif false {\n\t\tt.Note = d.Note\n\t}"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "E3-unreadable-is-not-undecided",
|
||
"why": "an UNREADABLE decision file must stay a loud error; only ABSENT means «nobody has decided anything yet» (the declared-path era is retired, the distinction that survives is absent-vs-unreadable)",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/mining.go",
|
||
"find": "\tdefault:\n\t\treturn false, fmt.Errorf(\"pipeline: %s is not readable: %w\", path, err)\n\t}",
|
||
"replace": "\tdefault:\n\t\treturn false, nil\n\t}"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "F-artifact-paths-absolute",
|
||
"why": "the published artifact paths are absolute; a consumer runs in another working directory, so a relative one names a different file",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/status.go",
|
||
"find": "\t\tMinedDelta: absPath(r.Book.MinedDelta),",
|
||
"replace": "\t\tMinedDelta: r.Book.MinedDelta,"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "G-byte-gate",
|
||
"why": "the second write gate is the BYTES. UNREACHABLE behind the first: *Touched is set only when a decision changed the document, and `next` is the render of the CHANGED document, so bytes-equal implies nothing-changed implies !*Touched. Kept as depth against a coarser future *Touched and pinned as a function contract (TestChangedDocIsTheSecondFence...), which no source mutation of the CALL can reach.",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/bankdecisions.go",
|
||
"find": "\tdelta = res.DeltaBytes != nil && res.DeltaTouched &&\n\t\tchangedDoc(st.deltaRaw, res.DeltaBytes, emptyDelta(res.Delta))",
|
||
"replace": "\tdelta = res.DeltaBytes != nil && res.DeltaTouched"
|
||
}
|
||
],
|
||
"expect": "survives"
|
||
},
|
||
{
|
||
"id": "H-decisions-travel-with-the-book",
|
||
"why": "the decision files default beside the BOOK, not beside project_db: they are the user's data and must travel with the book directory in a backup or an export",
|
||
"package": "./internal/config/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/config/book.go",
|
||
"find": "\tb.MinedDelta = filepath.Join(dir, b.BookID+MinedDeltaSuffix)",
|
||
"replace": "\tb.MinedDelta = filepath.Join(filepath.Dir(b.ProjectDB), b.BookID+MinedDeltaSuffix)"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "I-normalize-text",
|
||
"why": "the document the door writes is normalized, which is what keeps a lawful note with a leading newline writable at all",
|
||
"package": "./internal/membank/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/seed/normalize.go",
|
||
"find": "func normText(s string) string { return strings.TrimSpace(s) }",
|
||
"replace": "func normText(s string) string { return strings.TrimLeft(s, \"\") }"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "J-render-gate",
|
||
"why": "the render PROVES its bytes read back as the same document; without the equality half it only proves they parse",
|
||
"package": "./internal/membank/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/membank/decisions.go",
|
||
"find": "\tif reflect.DeepEqual(back, doc) {\n\t\treturn b, nil\n\t}",
|
||
"replace": "\tif reflect.DeepEqual(back, doc) || true {\n\t\treturn b, nil\n\t}"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "K-decision-text-trimmed",
|
||
"why": "the door trims the free text it accepts, the same rule it already applies to src/sense/dst",
|
||
"package": "./internal/membank/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/membank/decisions.go",
|
||
"find": "\t\td.Kind, d.Note = strings.TrimSpace(d.Kind), strings.TrimSpace(d.Note)",
|
||
"replace": "\t\td.Kind = strings.TrimSpace(d.Kind)"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "L-book-id-is-a-path-element",
|
||
"why": "book_id names four files; a separator in it walks the whole set out of the book's directory",
|
||
"package": "./internal/config/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/config/book.go",
|
||
"find": "\t} else if strings.ContainsAny(b.BookID, `/\\`) || b.BookID == \".\" || b.BookID == \"..\" {",
|
||
"replace": "\t} else if false {"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "M-owned-paths-distinct",
|
||
"why": "two config keys on one path means one document silently overwrites the other",
|
||
"package": "./internal/config/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/config/book.go",
|
||
"find": "\t\t\tif owned[i].path == \"\" || owned[i].path != owned[j].path {\n\t\t\t\tcontinue\n\t\t\t}",
|
||
"replace": "\t\t\tif true {\n\t\t\t\tcontinue\n\t\t\t}"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "N-flag-presence",
|
||
"why": "the flag guards key on the flag being PRESENT, not on its value being non-empty",
|
||
"package": "./cmd/tmctl/",
|
||
"edits": [
|
||
{
|
||
"file": "cmd/tmctl/invocation.go",
|
||
"find": "\tif given[\"decisions\"] {",
|
||
"replace": "\tif *decisions != \"\" {"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "O-reversed-window",
|
||
"why": "a chapter window that ends before it begins is written, loads, and fires nowhere",
|
||
"package": "./internal/membank/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/membank/decisions.go",
|
||
"find": "\tif key.Until != 0 && key.Since > key.Until {",
|
||
"replace": "\tif false {"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "P-alias-judged-on-the-set",
|
||
"why": "an inert decline is judged AFTER the fold, on the result of the whole call: the term that owns the alias may be approved by another decision of the same set (anchor re-pointed when refuseInertDeclines gained its ApplyInput argument; the attacked property — the ORDER of the two phases — is unchanged)",
|
||
"package": "./internal/membank/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/membank/decisions.go",
|
||
"find": "\tfoldAccepted(in, rs, &res)\n\trefuseInertDeclines(in, rs, &res)",
|
||
"replace": "\trefuseInertDeclines(in, rs, &res)\n\tfoldAccepted(in, rs, &res)"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "Q-note-not-erased-by-omission",
|
||
"why": "a repeat decision that carries no note keeps the note the owner wrote, in BOTH files",
|
||
"package": "./internal/membank/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/membank/decisions.go",
|
||
"find": "\t\tif note == \"\" {\n\t\t\tnext.Note = r.Note\n\t\t}",
|
||
"replace": "\t\tif false {\n\t\t\tnext.Note = r.Note\n\t\t}"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "R-decisions-cap",
|
||
"why": "the decision document is the one input a USER sizes; uncapped, the engine dies of memory and the platform reads that as a successful run",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/bankdecisions.go",
|
||
"find": "\tif len(raw) > maxDecisionsBytes {",
|
||
"replace": "\tif false {"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "S-stop-writes-nothing",
|
||
"why": "a SIGTERM between the decision and the write must leave the files untouched",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/bankdecisions.go",
|
||
"find": "\tif err := ctx.Err(); err != nil {\n\t\treturn finishReport(verdict, outcomeStopped, book, st, res, writtenFiles{}), err\n\t}",
|
||
"replace": "\tif err := ctx.Err(); err != nil && false {\n\t\treturn finishReport(verdict, outcomeStopped, book, st, res, writtenFiles{}), err\n\t}"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "T-problems-by-subject",
|
||
"why": "a pre-existing fault is recognised per SUBJECT; joined into one string, fixing one term makes every other term's fault look new",
|
||
"package": "./internal/membank/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/membank/decisions.go",
|
||
"find": "\t\tif errors.As(err, &sp) {\n\t\t\tproblems = append(problems, sp.Problems...)",
|
||
"replace": "\t\tif errors.As(err, &sp) && false {\n\t\t\tproblems = append(problems, sp.Problems...)"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "U-signature-honesty",
|
||
"why": "a call that leaves surfaces of the signature map undecided must not read as «done»",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/bankdecisions.go",
|
||
"find": "\t\tst.Surfaces++\n\t\tif !handled[nk] {\n\t\t\tst.Undecided++\n\t\t}",
|
||
"replace": "\t\tst.Surfaces++\n\t\tif !handled[nk] && false {\n\t\t\tst.Undecided++\n\t\t}"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "V-manifest-artifacts",
|
||
"why": "the artifact envelope reaches the surface the consumer actually calls",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/manifest.go",
|
||
"find": "\t\tChunksTotal: len(chunks), Artifacts: r.artifacts(),",
|
||
"replace": "\t\tChunksTotal: len(chunks),"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "W-decision-count-cap",
|
||
"why": "the COUNT of decisions is what holds the call inside a caller's timeout; the byte cap admits ~29 000 minimal declines and five minutes of work",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/bankdecisions.go",
|
||
"find": "\tif n := len(doc.Decisions); n > maxDecisions {",
|
||
"replace": "\tif n := len(doc.Decisions); false {"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "Y-subject-has-no-rendering",
|
||
"why": "a fault's subject is the term's IDENTITY; embedding the rendering makes a fault look new the moment the door changes that term's dst — and gender has no channel through which to fix it",
|
||
"package": "./internal/membank/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/membank/memseed.go",
|
||
"find": "\t\t\t\tSubject: subjectOf(\"gender\", e.Src, e.Sense, e.SinceCh, e.UntilCh),",
|
||
"replace": "\t\t\t\tSubject: fmt.Sprintf(\"%s\\u2192%s: gender %q\", e.Src, e.Dst, e.Gender),"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "Z-signature-unreadable",
|
||
"why": "a signature map that cannot be parsed must not report as «nothing left undecided»",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/bankdecisions.go",
|
||
"find": "\t\t// «everything is decided», which is the one answer this field must never give by accident.\n\t\treturn SignatureState{Map: absPath(path), Unreadable: true}\n\t}",
|
||
"replace": "\t\t// «everything is decided», which is the one answer this field must never give by accident.\n\t\treturn SignatureState{Map: absPath(path)}\n\t}"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "AB-alias-escape",
|
||
"why": "a decline is judged by what the surface DOES after the call; the «it was a term of its own» escape let through a decline that leaves the surface firing as another term's alias",
|
||
"package": "./internal/membank/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/membank/decisions.go",
|
||
"find": "\t\tif owner, held := aliasOwner(res.Delta, r.key.Src); held {",
|
||
"replace": "\t\tdroppedARow := false\n\t\tfor _, a := range res.Accepted {\n\t\t\tif a.Index == i {\n\t\t\t\tdroppedARow = len(a.Replaced) > 0\n\t\t\t}\n\t\t}\n\t\tif owner, held := aliasOwner(res.Delta, r.key.Src); held && !droppedARow {"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "AC-alias-owner-status",
|
||
"why": "only a SIGNED row excludes its surfaces from proposals, so declining the alias of a status:auto row is not inert — and row 199's first format mine is exactly that state",
|
||
"package": "./internal/membank/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/membank/decisions.go",
|
||
"find": "\t\tif st := strings.TrimSpace(t.Status); st != \"\" && st != \"approved\" {",
|
||
"replace": "\t\tif st := strings.TrimSpace(t.Status); st != \"\" && st != \"approved\" && false {"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "AD-loader-partial-set",
|
||
"why": "the loader hands back the entries it built even when it refuses; without them the checks below it cannot see a document that does not load, and repairing loadability reads as introducing everything the blindness was hiding",
|
||
"package": "./internal/membank/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/membank/memseed.go",
|
||
"find": "\t\t\t\tfmt.Sprintf(\"term %q: a %s term must have a non-empty dst (an empty one is silently inert; only status=auto may lack a dst)\", t.Src, status))",
|
||
"replace": "\t\t\t\tfmt.Sprintf(\"term %q: a %s term must have a non-empty dst (an empty one is silently inert; only status=auto may lack a dst)\", t.Src, status))\n\t\t\tcontinue"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "AE-positionless-subject",
|
||
"why": "the three «record N has no identity» messages key on a POSITION, which moves when an earlier record is removed; the explicit positionless subject is what keeps a shifted position from reading as a new fault",
|
||
"package": "./internal/membank/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/membank/problems.go",
|
||
"find": "func (p *problemList) addKeyed(subject, text string) {\n\t*p = append(*p, Problem{Subject: subject, Text: text})\n}",
|
||
"replace": "func (p *problemList) addKeyed(subject, text string) {\n\t_ = subject\n\t*p = append(*p, Problem{Subject: text, Text: text})\n}"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "AF-keys-file-reason-covers-all",
|
||
"why": "the reason a refusal gives must describe the command that receives it; a hand-written list left `bank-apply` — the verb this pack added — undescribed by its own refusal",
|
||
"package": "./cmd/tmctl/",
|
||
"edits": [
|
||
{
|
||
"file": "cmd/tmctl/invocation.go",
|
||
"find": "The $0 read commands (report/status/export/manifest/seed-lint/bank-apply) must not demand provider keys at all (D20.4)",
|
||
"replace": "The $0 read commands (report/status/export/manifest/seed-lint) must not demand provider keys at all (D20.4)"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "AG-stopped-report",
|
||
"why": "a call stopped by a signal must not PRINT that it applied — not in mode, not in changed, and not in the accepted list, which is the field a consumer iterates",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/bankdecisions.go",
|
||
"find": "\t\trep.Mode, rep.Changed, rep.Accepted = \"stopped\", false, []membank.AcceptedDecision{}",
|
||
"replace": "\t\trep.Mode, rep.Changed = \"apply\", true"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "AH-signature-read-error",
|
||
"why": "a signature map that exists but cannot be READ must not report as «nothing left undecided»",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/bankdecisions.go",
|
||
"find": "\t\tif errors.Is(err, fs.ErrNotExist) {\n\t\t\treturn SignatureState{} // no run has reached the bank boundary: nothing to count against\n\t\t}",
|
||
"replace": "\t\treturn SignatureState{}"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "AI-refused-warns-nothing",
|
||
"why": "canonical_rewrite warns about a mutation that is about to happen; a refused call performs none",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/bankdecisions.go",
|
||
"find": "\t\trep.Mode, rep.Changed = \"refused\", false\n\t\trep.Signature = signatureState(book, st.seed.Terms, docsFromDisk(st))",
|
||
"replace": "\t\trep.Mode, rep.Changed = \"refused\", false\n\t\twarn()\n\t\trep.Signature = signatureState(book, st.seed.Terms, docsFromDisk(st))"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "AJ-seed-alias-decline",
|
||
"why": "a decline of a seed ALIAS is inert only while the delta has no row of its own for that surface; when it does, the decline drops it and repairs the collision the report is listing (anchor re-pointed when the refusal gained its second carve-out; the attacked property is unchanged)",
|
||
"package": "./internal/membank/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/membank/decisions.go",
|
||
"find": "held &&\n\t\t\t!deltaHoldsSurface(in.Delta, r.key.Src) && !rejectsHoldSurface(in.Rejects, r.key.Src) {",
|
||
"replace": "held && !rejectsHoldSurface(in.Rejects, r.key.Src) {"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FX1-stop-memory-not-recorded",
|
||
"why": "a stop that does not RECORD what it presented re-stops on the same map forever — the flag stops paying once per novelty",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/mining.go",
|
||
"find": "\tif merr := r.Store.MarkStopPresented(r.Book.BookID, presentedSurfaces(mined)); merr != nil {",
|
||
"replace": "\tif merr := r.Store.MarkStopPresented(r.Book.BookID, nil); merr != nil {"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FX2-old-stop-semantics",
|
||
"why": "the pre-D39.144 formula — stop on ANY non-empty delta — is the exact regression §4.11 says no test guarded; the flag-model chain must redden on it",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/mining.go",
|
||
"find": "\t\tstopping = hasUnpresentedCluster(mined, presented)",
|
||
"replace": "\t\tstopping = len(mined) > 0 && len(presented) >= 0"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FX3-memory-by-representative-only",
|
||
"why": "a memory keyed on the representative alone re-stops when a cluster's first-ranked member changes to an alias of the same entity",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/mining.go",
|
||
"find": "\tfor _, a := range t.Aliases {\n\t\tif nk := text.NormalizeSourceKey(a); nk != \"\" {\n\t\t\tout = append(out, nk)\n\t\t}\n\t}\n\treturn out\n}\n\n// hasUnpresentedCluster",
|
||
"replace": "\t_ = t.Aliases\n\treturn out\n}\n\n// hasUnpresentedCluster"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FX4-auto-wire-gated-on-the-flag",
|
||
"why": "§4.3: a raised flag on a NON-stopping run must not switch the auto wire off — the unsigned rows must still ride to the editor",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/mining.go",
|
||
"find": "\t\tif err := r.writeAutoBank(ctx, mined, proposals, ownerHandled(unsignedEngineSurfaces(seed), rejects)); err != nil {\n\t\t\treturn false, err\n\t\t}",
|
||
"replace": "\t\tif !r.VerifyBank {\n\t\t\tif err := r.writeAutoBank(ctx, mined, proposals, ownerHandled(unsignedEngineSurfaces(seed), rejects)); err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t}"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FX5-normalize-one-trigger",
|
||
"why": "the canonical form trims the WHOLE whitespace class, not the one trigger (leading newline) the emitter defect made famous",
|
||
"package": "./internal/seed/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/seed/normalize.go",
|
||
"find": "func normText(s string) string { return strings.TrimSpace(s) }",
|
||
"replace": "func normText(s string) string { return strings.TrimLeft(s, \"\\n\") }"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FX6-subject-collapses-to-class",
|
||
"why": "a fault subject without identity lets any pre-existing fault of a class excuse every INTRODUCED fault of that class",
|
||
"package": "./internal/membank/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/membank/problems.go",
|
||
"find": "\treturn fmt.Sprintf(\"%s|%s|%s|%d|%d\", class, src, sense, since, until)",
|
||
"replace": "\treturn class"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FX7-subject-loses-the-window",
|
||
"why": "a fault subject without the chapter window masks a new fault on the SAME term in ANOTHER window",
|
||
"package": "./internal/membank/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/membank/problems.go",
|
||
"find": "\treturn fmt.Sprintf(\"%s|%s|%s|%d|%d\", class, src, sense, since, until)",
|
||
"replace": "\treturn fmt.Sprintf(\"%s|%s|%s\", class, src, sense)"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FX8-report-paths-relative",
|
||
"why": "the bank-apply report's file paths must be absolute — the consumer runs in its own working directory (the status report pins the same guarantee separately)",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/bankdecisions.go",
|
||
"find": "\tif abs, err := filepath.Abs(p); err == nil {\n\t\treturn abs\n\t}\n\treturn p",
|
||
"replace": "\treturn p"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FX9-byte-cap-boundary",
|
||
"why": "a document of exactly the byte cap is lawful; > tightened to >= refuses it",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/bankdecisions.go",
|
||
"find": "\tif len(raw) > maxDecisionsBytes {",
|
||
"replace": "\tif len(raw) >= maxDecisionsBytes {"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FX10-count-cap-boundary",
|
||
"why": "a document of exactly the count cap is lawful; > tightened to >= refuses it",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/bankdecisions.go",
|
||
"find": "\tif n := len(doc.Decisions); n > maxDecisions {",
|
||
"replace": "\tif n := len(doc.Decisions); n >= maxDecisions {"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FX12-absolute-paths-not-cleaned",
|
||
"why": "an uncleaned absolute path is a second spelling of a guarded path; the acceptance walked through exactly this and reproduced the data-loss blocker",
|
||
"package": "./internal/config/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/config/book.go",
|
||
"find": "\t\t\treturn filepath.Clean(p)",
|
||
"replace": "\t\t\treturn p"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FX13-retired-keys-honoured",
|
||
"why": "a declared decision-path key must fail naming the cure, never silently resolve to the convention while the operator believes the declared path is in force",
|
||
"package": "./internal/config/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/config/book.go",
|
||
"find": "\tif b.RetiredMinedDelta != \"\" || b.RetiredMinedRejects != \"\" {",
|
||
"replace": "\tif false && (b.RetiredMinedDelta != \"\" || b.RetiredMinedRejects != \"\") {"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FX14-map-truncated-in-place",
|
||
"why": "the signature map is read by the other side of the seam; os.WriteFile truncates first and a live reader gets a half-written map",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/mining.go",
|
||
"find": "\tif err := writeFileAtomic(r.signatureMapPath(), mapBytes); err != nil {",
|
||
"replace": "\tif err := os.WriteFile(r.signatureMapPath(), mapBytes, 0o644); err != nil {"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FX15-envelope-dropped",
|
||
"why": "the map's seam envelope (version + content id) is 17-seam-inbound-law п.3; without it two maps are indistinguishable without a diff",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/membank/decisions.go",
|
||
"find": "\tm := seed.SignatureMap{Version: seed.SignatureMapVersion, ID: id, File: content}",
|
||
"replace": "\tm := seed.SignatureMap{File: content}\n\t_ = id"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FX16-interleaved-commit",
|
||
"why": "§4.8: both documents are STAGED before either rename; committing the first before staging the second re-opens «half landed» for failures staging would catch with zero bytes moved",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/bankdecisions.go",
|
||
"find": "\tif rejectsChanged {\n\t\tif rejectsStage, err = stageFileAtomic(book.MinedRejects, res.RejectBytes); err != nil {\n\t\t\tif deltaStage != nil {\n\t\t\t\tdeltaStage.abort()\n\t\t\t}\n\t\t\treturn wrote, err\n\t\t}\n\t}",
|
||
"replace": "\tif deltaStage != nil {\n\t\tif err := deltaStage.commit(); err != nil {\n\t\t\treturn wrote, err\n\t\t}\n\t\twrote.delta = true\n\t\tdeltaStage = nil\n\t}\n\tif rejectsChanged {\n\t\tif rejectsStage, err = stageFileAtomic(book.MinedRejects, res.RejectBytes); err != nil {\n\t\t\treturn wrote, err\n\t\t}\n\t}"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FX17-dir-sync-skipped",
|
||
"why": "the decision files are the user's words: either the directory entries are flushed or the report says durability is unproven — silence is neither",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/bankdecisions.go",
|
||
"find": "\tif err := syncDir(filepath.Dir(book.MinedDelta)); err != nil {\n\t\treturn wrote, fmt.Errorf(\"the files are renamed but their directory entries are not proven durable (a host crash could still lose them): %w\", err)\n\t}",
|
||
"replace": "\t_ = syncDir"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FX18-write-incomplete-unmapped",
|
||
"why": "a refusal class without a number falls to exit 19 and the consumer loses the one thing the class buys — «re-send the same document»",
|
||
"package": "./cmd/tmctl/",
|
||
"edits": [
|
||
{
|
||
"file": "cmd/tmctl/main.go",
|
||
"find": "\tpipeline.RefusalDecisionsRejected: exitDecisionsRejected,\n\tpipeline.RefusalWriteIncomplete: exitWriteIncomplete,",
|
||
"replace": "\tpipeline.RefusalDecisionsRejected: exitDecisionsRejected,"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FX19-cap-report-dropped",
|
||
"why": "§4.7: every refusal of the decisions class prints a report, the caps included — the contract's two carriers must not diverge again",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/bankdecisions.go",
|
||
"find": "\t\t\treturn capRefusalReport(book, err), err",
|
||
"replace": "\t\t\treturn BankDecisionsReport{}, err"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FX20-write-failed-success-shape",
|
||
"why": "the acceptance's repro: EXIT=1 with a success-shaped report over a half-written pair; the write-failed outcome must never print «apply»",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/bankdecisions.go",
|
||
"find": "\t\trep.Mode, rep.Changed = \"write_incomplete\", files.delta || files.rejects",
|
||
"replace": "\t\trep.Mode, rep.Changed = \"apply\", true"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FX11-window-boundary",
|
||
"why": "a single-chapter window (since == until) is a lawful term of one chapter; > tightened to >= refuses it as a reversal",
|
||
"package": "./internal/membank/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/membank/decisions.go",
|
||
"find": "\tif key.Until != 0 && key.Since > key.Until {",
|
||
"replace": "\tif key.Until != 0 && key.Since >= key.Until {"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FX21-dry-run-silently-paid",
|
||
"why": "`tmctl translate --dry-run` used to parse cleanly and run a PAID translation — the flag's silent version inverts the one promise (--dry-run = spends nothing) the caller relies on",
|
||
"package": "./cmd/tmctl/",
|
||
"edits": [
|
||
{
|
||
"file": "cmd/tmctl/invocation.go",
|
||
"find": "\tif given[\"dry-run\"] && cmd != \"bank-apply\" && cmd != \"redrive\" {",
|
||
"replace": "\tif false && given[\"dry-run\"] && cmd != \"bank-apply\" && cmd != \"redrive\" {"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FX22-retry-never-reproves-durability",
|
||
"why": "the retry the write-incomplete class prescribes lands on the byte-no-op branch; without a directory sync there, the one thing exit 15 warned about is never re-proven and the retry answers 0",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/bankdecisions.go",
|
||
"find": "\t\tif err := syncDir(filepath.Dir(book.MinedDelta)); err != nil {\n\t\t\treturn finishReport(verdict, outcomeWriteFailed, book, st, res, writtenFiles{}),\n\t\t\t\trefuse(RefusalWriteIncomplete, fmt.Errorf(\"nothing was written by this call, but the directory holding the decision files still cannot be flushed — their durability is unproven: %w\", err))\n\t\t}",
|
||
"replace": "\t\t_ = syncDir"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FX23-map-write-failure-downgraded",
|
||
"why": "the map-before-memory ORDER is load-bearing: a run that proceeds past a failed map write stops pointing the owner at a file that is not there and marks the clusters presented — no later flag ever stops on them again (acceptance planting, survived three candidate packages)",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/mining.go",
|
||
"find": "\tif err := writeFileAtomic(r.signatureMapPath(), mapBytes); err != nil {\n\t\treturn false, fmt.Errorf(\"pipeline: the bank-mining stop write signature map %s: %w\", r.signatureMapPath(), err)\n\t}",
|
||
"replace": "\tif err := writeFileAtomic(r.signatureMapPath(), mapBytes); err != nil {\n\t\tr.Log.WarnContext(ctx, \"pipeline: the bank-mining stop write signature map failed\", \"err\", err)\n\t}"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FX24-write-failed-signature-from-intent",
|
||
"why": "the write-failed report must measure the signature against the DISK: fed the call's own result, a class-15 report whose write landed nothing prints undecided:0 — a success-shaped block on the one outcome the honesty work was done for (panel finding, reproduced)",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/bankdecisions.go",
|
||
"find": "\t\t// The POST-state, re-read from disk: for an outcome that touched the world the report describes\n\t\t// the files as they now are, never the bytes the call intended.\n\t\trep.Signature = signatureState(book, st.seed.Terms, docsReRead(book))",
|
||
"replace": "\t\t// The POST-state, re-read from disk: for an outcome that touched the world the report describes\n\t\t// the files as they now are, never the bytes the call intended.\n\t\trep.Signature = signatureState(book, st.seed.Terms, docsFromResult(st, res))"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "SH1-system-join",
|
||
"why": "an endpoint that carries ONE system message must receive the memory-bank injection INSIDE that message; without the join the glossary is dropped by the provider at HTTP 200 and no gate can see it",
|
||
"package": "./internal/llm/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/llm/httpllm.go",
|
||
"find": "\tif mode != SystemMessagesSingle {",
|
||
"replace": "\tif true {"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "SH2-system-axis-from-yaml",
|
||
"why": "the declared quirk has to survive the trip from models.yaml to the resolved Capability; dropped there, the join never runs and the loss is silent again",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/config/models.go",
|
||
"find": "\tdefault:\n\t\tc.SystemMessages = llm.SystemMessagesMode(cfg.SystemMessages)\n\t}",
|
||
"replace": "\tdefault:\n\t}"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "SH3-gap-marker",
|
||
"why": "a unit that ships text with a member chunk MISSING must say so in the text a human reads; without the marker the reader gets a seamless concatenation with a hole and no seam",
|
||
"package": "./cmd/tmctl/",
|
||
"edits": [
|
||
{
|
||
"file": "cmd/tmctl/render.go",
|
||
"find": "\tm := fmt.Sprintf(\"[⚠ TEXT MISSING — %d source %s of this unit could not be translated and %s NOT in the text below\",",
|
||
"replace": "\tm := fmt.Sprintf(\"[%d %s %s\","
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "SH4-dropped-members-counted",
|
||
"why": "the incompleteness fact is carried by DroppedMembers, not inferred from a flag reason; stop counting it and both renderers go back to guessing, which is how the reader was told a member drop was a cosmetic clean-up",
|
||
"package": "./cmd/tmctl/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/export.go",
|
||
"find": "\tce.DroppedMembers = len(drops)",
|
||
"replace": "\tce.DroppedMembers = 0"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "SH5-decline-converges",
|
||
"why": "a decline already on the record is a decision being RE-SENT, not one being made; without this carve-out the door refuses the identical document forever and a worker that retries splits the state",
|
||
"package": "./internal/membank/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/membank/decisions.go",
|
||
"find": " && !rejectsHoldSurface(in.Rejects, r.key.Src) {",
|
||
"replace": " {"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "SH6-rejects-rename-first",
|
||
"why": "the delta is the document the seed-conflict refusal READS, so it must not be the one that lands first: a decline interrupted after a delta-first rename leaves a state no predicate can tell from an inert decline, and the re-send is refused forever",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/bankdecisions.go",
|
||
"find": "\tif rejectsStage != nil {\n\t\tif err := rejectsStage.commit(); err != nil {\n\t\t\tif deltaStage != nil {\n\t\t\t\tdeltaStage.abort()\n\t\t\t}\n\t\t\treturn wrote, err\n\t\t}\n\t\twrote.rejects = true\n\t}\n\tif deltaStage != nil {\n\t\tif err := deltaStage.commit(); err != nil {\n\t\t\treturn wrote, err\n\t\t}\n\t\twrote.delta = true\n\t}",
|
||
"replace": "\tif deltaStage != nil {\n\t\tif err := deltaStage.commit(); err != nil {\n\t\t\tif rejectsStage != nil {\n\t\t\t\trejectsStage.abort()\n\t\t\t}\n\t\t\treturn wrote, err\n\t\t}\n\t\twrote.delta = true\n\t}\n\tif rejectsStage != nil {\n\t\tif err := rejectsStage.commit(); err != nil {\n\t\t\treturn wrote, err\n\t\t}\n\t\twrote.rejects = true\n\t}"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "SH7-drop-reason-is-the-holes-own",
|
||
"why": "the marker must name why the MEMBER dropped, not the unit's flag reason: a unit whose edit flagged for a cosmetic strip AND lost a member would otherwise tell the reader a clean-up ate a chunk of the book — the same false claim the pack removes, moved into the prose",
|
||
"package": "./cmd/tmctl/",
|
||
"edits": [
|
||
{
|
||
"file": "cmd/tmctl/render.go",
|
||
"find": "gapMarker(ce.DroppedMembers, ce.DroppedReason)",
|
||
"replace": "gapMarker(ce.DroppedMembers, ce.FlagReason)"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "SH8-draft-only-has-no-members",
|
||
"why": "a draft-only pipeline makes every chunk a SINGLETON unit whose own draft row is its final row, so the member-drop rule reads the unit's own flag as a lost member; without the guard a cosmetically stripped chunk that lost NOTHING is announced as incomplete — a marker that misinforms, which the order forbids as firmly as the silence it replaces",
|
||
"package": "./cmd/tmctl/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/export.go",
|
||
"find": "\tif r.finalStageWave() == waveEdit {",
|
||
"replace": "\tif true {"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "SH9-shipped-gemini-declaration",
|
||
"why": "the two lines in configs/models.yaml are the ONLY thing that makes the system-message join reach the real endpoint; everything else about the fix is exercised against a synthetic fixture provider, and deleting them used to leave the whole module green",
|
||
"package": "./internal/config/",
|
||
"edits": [
|
||
{
|
||
"file": "configs/models.yaml",
|
||
"find": " capabilities:\n system_messages: single\n",
|
||
"replace": ""
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "SH10-translate-empty-text-guard",
|
||
"why": "renderTranslate's non-empty-text guards are what stop the gap marker pointing at text that does not exist; the code calls them load-bearing and nothing pinned them",
|
||
"package": "./cmd/tmctl/",
|
||
"edits": [
|
||
{
|
||
"file": "cmd/tmctl/render.go",
|
||
"find": "\t\tcase ch.DroppedMembers > 0 && ch.FinalText != \"\":",
|
||
"replace": "\t\tcase ch.DroppedMembers > 0:"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "SH11-explicit-multi-costs-nothing",
|
||
"why": "writing the DEFAULT out loud must not put a key in the capability the snapshot carries — otherwise a line that changes no byte on the wire re-buys the book",
|
||
"package": "./internal/config/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/config/models.go",
|
||
"find": "\tcase \"multi\":\n\t\tc.SystemMessages = llm.SystemMessagesMulti",
|
||
"replace": "\tcase \"multi\":\n\t\tc.SystemMessages = llm.SystemMessagesMode(\"multi\")"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "SH12-declined-never-enters-the-bank",
|
||
"why": "the rejects-first rename order leaves the delta row on disk so an interrupted decline can converge; without this filter that window is one in which a PAID run injects a term the owner explicitly declined",
|
||
"package": "./internal/pipeline/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/mining.go",
|
||
"find": "\t\t\tif rejects[text.NormalizeSourceKey(e.Src)] {",
|
||
"replace": "\t\t\tif false {"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "SH13-inert-decline-second-door",
|
||
"why": "the ALIAS door refuses an inert decline for its own good reason, but a decline ALREADY on the record is a decision being re-sent; unnarrowed it refused the standing ledger forever and, the layer being all-or-nothing, discarded the lawful decisions sent beside it",
|
||
"package": "./internal/membank/",
|
||
"edits": [
|
||
{
|
||
"file": "internal/membank/decisions.go",
|
||
"find": "\t\tif rejectsHoldSurface(in.Rejects, r.key.Src) {\n\t\t\tcontinue\n\t\t}\n",
|
||
"replace": ""
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FC1-book-filter-store",
|
||
"why": "CheckpointUsageForBook's `WHERE j.book_id = ?` is the only thing keeping another book's checkpoints out of the consent number when two books share a project database; removing it left every store and money test green (backlog row 197, ФЧ-1 — reproduced by the acceptance), because no fixture had two books at one position",
|
||
"package": "./internal/store/",
|
||
"run": "TestCheckpointUsageForBookIsScopedToTheBook",
|
||
"edits": [
|
||
{
|
||
"file": "internal/store/ledger.go",
|
||
"find": "\t\tWHERE j.book_id = ?\n\t\tORDER BY c.rowid",
|
||
"replace": "\t\tWHERE j.book_id = ? OR 1 = 1\n\t\tORDER BY c.rowid"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FC1-book-filter-pipeline",
|
||
"why": "the same planting judged where the money is published: another book's calls at the same positions, ten times the price, must not overshoot this book's rows into «quoted from history» or move its projected book cost",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestAnotherBooksMoneyNeverEntersTheConsentNumber",
|
||
"edits": [
|
||
{
|
||
"file": "internal/store/ledger.go",
|
||
"find": "\t\tWHERE j.book_id = ?\n\t\tORDER BY c.rowid",
|
||
"replace": "\t\tWHERE j.book_id = ? OR 1 = 1\n\t\tORDER BY c.rowid"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FC1-cell-key",
|
||
"why": "the repricer's cell is the POSITION (chapter AND chunk); keyed by chapter alone it pools the neighbours' calls and the newest-first walk eats the neighbour's money — invisible while every money fixture had one chunk per chapter (backlog row 197, ФЧ-1)",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestEveryChunkOfAChapterIsRePricedFromItsOwnCalls",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/reprice.go",
|
||
"find": "\t\tkey := chunkKey{cu.Chapter, cu.ChunkIdx}",
|
||
"replace": "\t\tkey := chunkKey{cu.Chapter, 0}"
|
||
},
|
||
{
|
||
"file": "internal/pipeline/reprice.go",
|
||
"find": "\tcalls := rp.cells[chunkKey{cs.Chapter, cs.ChunkIdx}][cs.Stage]",
|
||
"replace": "\tcalls := rp.cells[chunkKey{cs.Chapter, 0}][cs.Stage]"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FC1-unpriced-not-disclosed",
|
||
"why": "a call carried at its billed figure (no usable usage) must mark the row as quoted from history; without the mark the amount stays right and every disclosure of «part of this sum is old money» — consent text, status --json, CLI — goes silent (backlog row 197, ФЧ-1 / ФЧ-4)",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestABilledDecodeRowIsDisclosedAsOldMoneyEndToEnd|TestRepricerCountsOnlyTheCurrentGenerationOfCheckpoints",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/reprice.go",
|
||
"find": "\t\tif !calls[i].priced {\n\t\t\tfromHistory = true\n\t\t}\n",
|
||
"replace": ""
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FC5-carry-unbounded",
|
||
"why": "the volume grant admits units an earlier run started and never shipped OUTSIDE the grant, and that carry must be bounded BY the grant; unbounded, a run that drafted a whole book hands every unit to the next purchase and --max-units stops bounding anything (measured on a fixture: a grant of one paid for five units)",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestTheCarryIsBoundedByTheGrant",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/volume.go",
|
||
"find": "\t\tif s.stop.Carried >= r.MaxUnits {\n\t\t\ts.stop.LeftFresh++\n\t\t\tcontinue\n\t\t}\n",
|
||
"replace": ""
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FC5-model-axis-configured-slot",
|
||
"why": "the re-payment prices a stored call at the model its stage RESOLVES to (label routing applied), not at the configured `model:` key; reading the configured slot prices a labelled book's re-payment at a model the run never calls, and no other test in the tree tells the two slots apart",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestTheModelAxisReadsTheRESOLVEDSlotNotTheConfiguredOne",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/reprice.go",
|
||
"find": "\t\tif escalation && st.ResolvedHop != \"\" {\n\t\t\treturn st.ResolvedHop, true\n\t\t}\n\t\treturn st.ResolvedModel, st.ResolvedModel != \"\"",
|
||
"replace": "\t\tif escalation && st.EscalateTo != \"\" {\n\t\t\treturn st.EscalateTo, true\n\t\t}\n\t\treturn st.Model, st.Model != \"\""
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FC6-reconcile-fresh-branch",
|
||
"why": "reconcile's docstring claims EVERY branch keeps Carried inside Delivered, and granted() = Delivered - Carried + Reworked depends on it; the fresh branch reverted to `Delivered > 0` drives the grant arithmetic negative and prints «0 NEW unit(s) delivered» beside «1 of the new ones had been started by an earlier run» — a sentence that cannot be true",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestReconcileKeepsContainmentOnTheFreshBranch",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/volume.go",
|
||
"find": "\t\tcase class == unitFresh && s.stop.Delivered > s.stop.Carried:",
|
||
"replace": "\t\tcase class == unitFresh && s.stop.Delivered > 0:"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FC6-capped-refusal-borrows-book-basis",
|
||
"why": "the --accept-rebill=<usd> refusal quotes THIS run's figure, so it must carry THIS run's basis; borrowing the book's tells a caller sizing their cap that more units are an upper estimate than the number beside it covers. Its twin, the threshold refusal's scoped clause, was pinned from the start; this branch was not",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestTheScopedClauseOfTheRefusalCarriesItsOwnBasis",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/rebill.go",
|
||
"find": "\t\t\tproj.USD, proj.Rows, projectionBasis(proj), r.AcceptRebill.CapUSD, r.AcceptRebill.CapUSD)",
|
||
"replace": "\t\t\tproj.USD, proj.Rows, projectionBasis(book), r.AcceptRebill.CapUSD, r.AcceptRebill.CapUSD)"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FC6-model-caveat-printed-at-zero",
|
||
"why": "a caveat that prints when its count is zero puts «0 of the N unit(s) were bought from a model their stage no longer resolves to» into EVERY consent text; a reader who learns to skip it skips it when it means something. The old-money twin has had this negative pin since it was written",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestProjectionBasisNamesWhatTheAmountIsMadeOf",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/rebill.go",
|
||
"find": "\tif p.ModelMovedRows > 0 {",
|
||
"replace": "\tif p.ModelMovedRows >= 0 {"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FC6-escalation-flag-store",
|
||
"why": "the re-pricing reads the escalation flag through CheckpointUsageForBook to know that a hop call's «today» is the stage's current escalate_to; dropping the column from the SELECT survives the store's own battery and only the pipeline's goes red — the same one-sided seam the book filter is pinned on both sides for",
|
||
"package": "./internal/store/",
|
||
"run": "TestCheckpointUsageCarriesTheEscalationFlag",
|
||
"edits": [
|
||
{
|
||
"file": "internal/store/ledger.go",
|
||
"find": "\t\t c.escalation\n\t\tFROM checkpoints c JOIN jobs j ON j.id = c.job_id",
|
||
"replace": "\t\t 0\n\t\tFROM checkpoints c JOIN jobs j ON j.id = c.job_id"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FC7-volume-stop-silent-outside-grant",
|
||
"why": "a run that finished units an earlier run began delivered MORE than its grant, and the stop line is the only channel that says why; judged by the remainder alone the report goes silent in exactly the case with the largest discrepancy",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestARunThatWorkedOutsideItsGrantSaysSo",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/volume.go",
|
||
"find": "func (s *volumeScope) bound() bool { return s != nil && (s.stop.Left() > 0 || s.stop.Carried > 0) }",
|
||
"replace": "func (s *volumeScope) bound() bool { return s != nil && s.stop.Left() > 0 }"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FC7-reconcile-free-branch",
|
||
"why": "the second fall-through of reconcile must keep Carried inside Delivered too; the guard removed there drives granted() negative on a book with one carried unit and one free unit that flags on resume",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestReconcileKeepsCarriedInsideDelivered",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/volume.go",
|
||
"find": "\t\tcase s.stop.Delivered > s.stop.Carried:\n\t\t\ts.stop.Delivered--\n\t\tcase s.stop.Carried > 0:\n\t\t\ts.stop.Delivered--\n\t\t\ts.stop.Carried--\n",
|
||
"replace": "\t\tcase s.stop.Delivered > 0:\n\t\t\ts.stop.Delivered--\n"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FC7-zero-usage-row-silent-after-a-move",
|
||
"why": "a $0 row with no usage on file cannot be re-priced once its stage resolves elsewhere — there are no tokens to price at the new model; publishing a bare $0 with no caveat is the silent under-quote D39.150 п.1 forbids",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestRepriceCheckpointFallsBackToTheBilledAmount",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/reprice.go",
|
||
"find": "\t\t\ttoday, ok := r.currentModelFor(cu.Stage, cu.Escalation)\n\t\t\treturn 0, !ok || today == cu.ModelRequested, false",
|
||
"replace": "\t\t\treturn 0, true, false"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FC7-scoped-clause-without-basis",
|
||
"why": "the threshold refusal prints the BOOK's figure and THIS run's slice; each travels with the basis of the projection it came from, because which of the slice's units are an upper estimate is not something the book's basis states for them",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestTheScopedClauseOfTheRefusalCarriesItsOwnBasis",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/rebill.go",
|
||
"find": "\t\t\tproj.Rows, proj.USD, projectionBasis(proj), scopedRepin)",
|
||
"replace": "\t\t\tproj.Rows, proj.USD, scopedRepin)"
|
||
},
|
||
{
|
||
"file": "internal/pipeline/rebill.go",
|
||
"find": "would re-pay %d of them, ~$%.6f (%s)%s — but the threshold",
|
||
"replace": "would re-pay %d of them, ~$%.6f%s — but the threshold"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FC7-overshoot-claims-a-model-move",
|
||
"why": "the revert/overshoot branch quotes a row at what it was BILLED, which is priced at no model's current table; counting it among the units priced across a model move inflates the caveat with rows the caveat does not describe",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestRepricerCountsOnlyTheCurrentGenerationOfCheckpoints",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/reprice.go",
|
||
"find": "\t\treturn cs.CostUSD, true, false\n\t}\n\tif residue := cs.CostUSD - accounted; residue > residueEpsilonUSD {",
|
||
"replace": "\t\treturn cs.CostUSD, true, moved\n\t}\n\tif residue := cs.CostUSD - accounted; residue > residueEpsilonUSD {"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FC8-free-call-dropped-from-a-paid-generation",
|
||
"why": "a call that cost NOTHING can neither complete a row's cost nor overshoot it, so a walk that stops on money drops it — and with it the price that call carries today. A $0 primary whose PAID escalation hop alone accounts for the row (a local model with a cloud fallback) is then quoted at half what the re-run buys, with no caveat: the silent under-quote D39.150 п.1 forbids",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestRepricerCountsOnlyTheCurrentGenerationOfCheckpoints",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/reprice.go",
|
||
"find": "\t\tfree := calls[i].then <= residueEpsilonUSD\n\t\tif accounted >= cs.CostUSD-residueEpsilonUSD && !free {",
|
||
"replace": "\t\tfree := calls[i].then <= residueEpsilonUSD\n\t\t_ = free\n\t\tif accounted >= cs.CostUSD-residueEpsilonUSD {"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FC8-unestablished-zero-row-published-as-free",
|
||
"why": "a $0 row whose newest call on file belongs to an older generation cannot be caught by the overshoot test — no walk overshoots $0 — so without this it is published as a bare $0 with no caveat; a `skipped` row is genuinely free, any other is one whose generation could not be established",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestRepricerCountsOnlyTheCurrentGenerationOfCheckpoints",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/reprice.go",
|
||
"find": "\tif taken == 0 && len(calls) > 0 && cs.Disposition != string(DispSkipped) {\n\t\tfromHistory = true\n\t}\n",
|
||
"replace": ""
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FC8-log-fork-at-the-end-of-the-book",
|
||
"why": "the run log takes the same fork as the operator line: a run that reached the end of the book did NOT stop short of it, and saying so of every bounded run makes the sentence worthless exactly where the grant behaved unusually",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestTheRunLogSaysWhichKindOfVolumeStopItWas",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/waverun.go",
|
||
"find": "\t\tif scope.stop.Left() == 0 {\n\t\t\tmsg = \"the run reached the end of the book under a VOLUME ceiling that held nothing back; it is reported because units an earlier run began were finished outside this grant\"\n\t\t}\n",
|
||
"replace": ""
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "FC8-empty-cell-disclosed-as-unestablished",
|
||
"why": "the «generation could not be established» mark is for a $0 row whose calls belong to somebody else, NOT for a row with no call at all — that one is free by construction; without the guard every such row inflates the count of units the operator is told could not be re-priced",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestRepricerCountsOnlyTheCurrentGenerationOfCheckpoints",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/reprice.go",
|
||
"find": "\tif taken == 0 && len(calls) > 0 && cs.Disposition != string(DispSkipped) {",
|
||
"replace": "\tif taken == 0 && cs.Disposition != string(DispSkipped) {"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "WB1-head-always-end-of-book",
|
||
"why": "the stop line's two openings are a fork, and only the NEW opening was held: a fork that reaches the end-of-book wording unconditionally passed the whole tree, and in a run whose grant really did cut it short it printed «reached the END of the book … Still in the book: 1 unit(s) NEVER delivered» — the book is finished and a unit of it was never delivered, in one sentence",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestTheStopLineSaysTheGrantHeldWorkBackWhenItDid",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/volume.go",
|
||
"find": "\tif v.Left() == 0 {\n\t\thead = \"reached the END of the book",
|
||
"replace": "\tif v.Left() >= 0 {\n\t\thead = \"reached the END of the book"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "WB2-head-keyed-on-the-carry",
|
||
"why": "the same fork asked about the wrong quantity. Which opening applies is the REMAINDER — did the grant hold work back — not the CARRY, which is why the report is attached at all. Keyed on the carry the fork is right on every state the tree exercised and wrong on the mixed one the carry itself made reachable: work carried AND work held back",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestTheStopLineSaysTheGrantHeldWorkBackWhenItDid",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/volume.go",
|
||
"find": "\tif v.Left() == 0 {\n\t\thead = \"reached the END of the book",
|
||
"replace": "\tif v.Carried > 0 {\n\t\thead = \"reached the END of the book"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "WB3-bound-border-drops-a-single-carry",
|
||
"why": "bound() reports a run that did work OUTSIDE its grant, and the smallest such run is ONE interrupted unit finished at the end of the book. A `> 1` border drops exactly that one: the run delivers a unit it never paid a slot for and returns no report at all — the silent limit D39.165 §1б names. The existing carry test finishes TWO units and survives the border",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestOneCarriedUnitIsStillWorkOutsideTheGrant",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/volume.go",
|
||
"find": "return s != nil && (s.stop.Left() > 0 || s.stop.Carried > 0) }",
|
||
"replace": "return s != nil && (s.stop.Left() > 0 || s.stop.Carried > 1) }"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "WB4-cut-tag-drops-the-embedded-data",
|
||
"why": "the cut tag rides inside every unit id, and a component dropped from it is invisible by construction — the id still looks like an id while it now points at different text. The embedded language data decides where boundaries fall (CJK numerals, sentence terminators), so without it an injection.txt edit re-cuts the whole book and re-mints nothing. Found by planting: removing it left the ENTIRE battery green, and this pack's point 3 chose the Go render over the data precisely because that component is load-bearing",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestTheCutTagActuallyCarriesTheEngineData",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/manifest.go",
|
||
"find": "\t\tChunker: chunkerVersion, Segmentation: r.segmentationSnapshot(), ShippingWave: r.shippingWaveTag(),\n\t\tLangpack: r.packVersion(), Embedded: lang.EmbeddedVersion(), Norm: text.NormVersion(),",
|
||
"replace": "\t\tChunker: chunkerVersion, Segmentation: r.segmentationSnapshot(), ShippingWave: r.shippingWaveTag(),\n\t\tLangpack: r.packVersion(), Embedded: \"\", Norm: text.NormVersion(),"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "WB5-target-screen-never-sees-the-target",
|
||
"why": "the target-language screen is handed the target's declared scripts at ONE call site; without them it abstains, so the verdict row 46 exists for is never reached on the live path while every offline test of the module still passes. Found by planting: the whole battery stayed green",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestTheRunnerHandsTheScreenItsData",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/chunkrun.go",
|
||
"find": "\t\tSourceScripts: r.checkers.SourceScripts(), TargetScripts: lang.LangScripts(r.Book.TargetLang),",
|
||
"replace": "\t\tSourceScripts: r.checkers.SourceScripts(),"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "WB6-off-target-flag-not-escalatable",
|
||
"why": "D39.93 п.2 refused to reuse FlagCJKArtifact precisely so the new flag would need an EXPLICIT entry in escalatable(). Without it a caught off-target chunk is silently LOST instead of re-attacked on another model — the failure the whitelist exists to prevent, and the one the design was warned about before the code was written",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestAnOffTargetCompletionIsFlaggedAndEscalated",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/disposition.go",
|
||
"find": "\tcase FlagCJKArtifact, FlagOffTargetLang, FlagExcisionSuspect, FlagLoopDegenerate,",
|
||
"replace": "\tcase FlagCJKArtifact, FlagExcisionSuspect, FlagLoopDegenerate,"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "WB7-bank-role-exemption-narrows-to-one-role",
|
||
"why": "the exemption is the role SET: a bank role answers a term table, whose letters are engine identifiers and source terms, so BOTH the echo rule and the target screen fire on every healthy batch. It was `role == roleTerminologist` for as long as no shipping config ran the classifier; row 140 runs it, so the regression goes live with it",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestTheRunnerHandsTheScreenItsData",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/terminologist.go",
|
||
"find": "\treturn role == roleTerminologist || role == roleClassifier",
|
||
"replace": "\treturn role == roleTerminologist"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "WB8-latin-residue-runs-on-a-latin-target",
|
||
"why": "the Latin-residue detector reads a run of Latin words as an untranslated leak — true for a Cyrillic target, inverted for a Latin-written one, where it drops every chunk of clean target prose. The guard was declared in a doccomment two layers up for years while standing only on the repair path",
|
||
"package": "./internal/checks/",
|
||
"run": "TestTheLatinResidueClassIsGatedOnTheTargetsOwnScript",
|
||
"edits": [
|
||
{
|
||
"file": "internal/checks/sanitizer.go",
|
||
"find": "\tif c.TargetScriptNonLatin() {\n\t\tif det := detectLatinInsertion(glossMasked); det != \"\" {",
|
||
"replace": "\tif true {\n\t\tif det := detectLatinInsertion(glossMasked); det != \"\" {"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "WB9-contour-configured-and-inert",
|
||
"why": "both bank roles run INSIDE the bank-mining stop, which returns before building a single candidate without a contrast artifact. The loader refuses the unset key; without that refusal a fully budgeted contour is configured and never executes, the class this loader refuses everywhere else",
|
||
"package": "./internal/config/",
|
||
"run": "TestABankContourThatCanNeverFireIsRefused",
|
||
"edits": [
|
||
{
|
||
"file": "internal/config/pipeline.go",
|
||
"find": "\t\tif p.Mining.ContrastPath == \"\" {\n\t\t\tbad(\"gates.terminology is enabled but `mining.contrast_path` is not set",
|
||
"replace": "\t\tif false {\n\t\t\tbad(\"gates.terminology is enabled but `mining.contrast_path` is not set"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "WB10-missing-contrast-costs-a-wave",
|
||
"why": "the run opens the contrast artifact at the bank-mining stop, AFTER the whole draft wave is bought. The write-path preflight is what turns «a deployment forgot the artifact» from a wave's money into a free refusal naming the path",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestTheContourRefusesAWriteRunItCannotExecute",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/runner.go",
|
||
"find": "\t\tif err := pipe.CheckMiningContrast(); err != nil {\n\t\t\treturn nil, refuse(RefusalBadConfig, err)\n\t\t}",
|
||
"replace": ""
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "WB11-nil-pack-contour-never-runs",
|
||
"why": "the OTHER condition of the same early return, and the one a config loader structurally cannot see: a book with no langpack_root, or one whose pair has no catalog directory, gets a nil pack and the stop returns before any candidate exists. The gate is known to the pipeline and the pack to the book; only the runner holds both",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestTheContourRefusesAWriteRunItCannotExecute",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/runner.go",
|
||
"find": "\tif forWrite && r.Pipeline.Gates.Terminology.Enabled && r.pack == nil {",
|
||
"replace": "\tif false && r.Pipeline.Gates.Terminology.Enabled && r.pack == nil {"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "WB12-two-orders-for-one-term",
|
||
"why": "the editor's law block gives ONE order per source term. Deduping by (src,dst) instead lets a signed row and the engine's own consolidation of the same surface stand side by side — two orders for one term, and with different gender directives, two contradictory orders about one character. Reachable and reported: the run already warns about exactly this conflict",
|
||
"package": "./internal/membank/",
|
||
"run": "TestTheLawBlockGivesOneOrderPerSourceTerm",
|
||
"edits": [
|
||
{
|
||
"file": "internal/membank/memory.go",
|
||
"find": "\t\tif seen[src] {\n\t\t\tcontinue\n\t\t}\n\t\tseen[src] = true",
|
||
"replace": "\t\tif seen[src+\"\\x00\"+dst] {\n\t\t\tcontinue\n\t\t}\n\t\tseen[src+\"\\x00\"+dst] = true"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "WB13-placeholder-refusal-arrives-after-a-paid-wave",
|
||
"why": "Render refuses an unknown placeholder per stage per chunk MID-RUN, so a prompt pack carrying a retired marker fails on the first call of that stage — for an editor prompt, after the whole draft wave is bought (measured on the {{genre}} retirement). The load-time scan makes the same refusal free. 27 eval/ prompt files still carry {{genre}}",
|
||
"package": "./internal/pipeline/",
|
||
"run": "TestRenderRejectsUnknownPlaceholder",
|
||
"edits": [
|
||
{
|
||
"file": "internal/pipeline/render.go",
|
||
"find": "\tif err := CheckPlaceholders(canon); err != nil {\n\t\treturn nil, fmt.Errorf(\"pipeline: prompt %s: %w\", path, err)\n\t}",
|
||
"replace": ""
|
||
}
|
||
]
|
||
}
|
||
]
|