63 lines
3.4 KiB
Go
63 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
|
|
"textmachine/backend/internal/pipeline"
|
|
)
|
|
|
|
// bankapply.go: `tmctl bank-apply` — the door a user's correction of the memory bank comes in through.
|
|
//
|
|
// It is the engine's half of D39.156: the bank is the product's consistency mechanism, and until this
|
|
// command existed the two files that carry a decision had no writer anywhere, so a platform could only
|
|
// deliver a user's correction by pretending to be a human with a text editor. The engine is now the sole
|
|
// writer of its own schema, which is what lets a caller decide about a term without knowing the format
|
|
// of anything.
|
|
//
|
|
// $0 and key-less, like the other read-side commands: it reads the bank, rewrites two YAML files and
|
|
// calls no provider. The flag that carries provider keys is refused for it at parse time (D20.4).
|
|
|
|
// bankApply runs the verb and prints its report to stdout.
|
|
//
|
|
// The report is printed for every outcome that HAS one, and that is the contract rather than a
|
|
// convenience: all-or-nothing means a refused set leaves the files untouched, so the list of reasons IS
|
|
// the product of the call. It goes to stdout while the one-line error goes to stderr — the same split
|
|
// `status --json` uses when it prints a whole report and then exits non-zero.
|
|
//
|
|
// ⚠ The DECISIONS class (14) prints a report in EVERY spelling, the two caps included — the class's own
|
|
// contract, brought to one carrier. What still speaks on stderr alone: the config class before the lock
|
|
// (10 — the wiring itself is broken, there is no book to report about), a project another process holds
|
|
// (12), a project whose schema is not this binary's (13 — the read of the bank is what discovers it), a
|
|
// stop that lands on the post-read cancellation check (5 — the verdict does not exist yet, so there are
|
|
// no computed facts to print; the later pre-write check DOES print the stopped report), and a plain
|
|
// read error on the store (1). A stop earlier than the post-read check is not observed at all: neither
|
|
// the config load nor the lock consults the context, so the call simply proceeds to that check.
|
|
// Class 12 is the one an automated caller meets most often, because it is the class it retries. Class 15 (write incomplete)
|
|
// always carries a report — its `written_*` fields are the difference between «nothing landed» and
|
|
// «half landed», and re-sending the same document converges either way.
|
|
func bankApply(ctx context.Context, out io.Writer, cfgPath, decisionsPath string, dryRun bool) error {
|
|
rep, err := pipeline.ApplyBankDecisions(ctx, cfgPath, decisionsPath, dryRun)
|
|
// A zero report means the call did not get far enough to have one (an unreadable config, a document
|
|
// that is not a decisions document); those errors speak for themselves on stderr.
|
|
if rep.Version != "" {
|
|
if werr := renderBankDecisions(out, rep); werr != nil && err == nil {
|
|
return werr
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// renderBankDecisions writes the report as indented JSON with a trailing newline — the shape the other
|
|
// machine surfaces of this CLI use (manifest --json, status --json), so a consumer parses all of them
|
|
// the same way.
|
|
func renderBankDecisions(out io.Writer, rep pipeline.BankDecisionsReport) error {
|
|
body, err := json.MarshalIndent(rep, "", " ")
|
|
if err != nil {
|
|
return fmt.Errorf("tmctl: render the bank-decisions report: %w", err)
|
|
}
|
|
_, err = fmt.Fprintf(out, "%s\n", body)
|
|
return err
|
|
}
|