textmachine/backend/cmd/tmmutate/mutations.json

728 lines
No EOL
30 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",
"package": "./internal/membank/",
"edits": [
{
"file": "internal/membank/decisions.go",
"find": "\tfoldAccepted(in, rs, &res)\n\trefuseInertDeclines(rs, &res)",
"replace": "\trefuseInertDeclines(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",
"package": "./internal/membank/",
"edits": [
{
"file": "internal/membank/decisions.go",
"find": "held && !deltaHoldsSurface(in.Delta, r.key.Src) {",
"replace": "held {"
}
]
},
{
"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))"
}
]
}
]