package config import ( "bytes" "encoding/json" "fmt" "log/slog" "os" "regexp" "sort" "strings" "testing" "time" "textmachine/platform/internal/books" "textmachine/platform/internal/pgstore" ) // PD-114. An environment-only configuration leaves no artifact to read back, so the boot line IS the // artifact — and the two things it must never carry are a secret and a sum of money. func effective(t *testing.T) (map[string]Setting, string) { t.Helper() c, err := Load() if err != nil { t.Fatal(err) } var buf bytes.Buffer c.LogEffective(slog.New(slog.NewJSONHandler(&buf, nil))) by := map[string]Setting{} for _, s := range c.Settings { if _, dup := by[s.Key]; dup { t.Errorf("%s is printed twice", s.Key) } by[s.Key] = s } return by, buf.String() } // The exemption `loggedFields` grants is exactly ONE field of ONE line, and nothing deeper. // // It exists because the first version of that helper skipped any key named `time` at any DEPTH, so a // nested group called `time` would have been silently exempt from the money check — a hole aimed the // wrong way, in a helper whose entire purpose is that no field escapes. Flat lines made the two // behave identically, which is how such a hole survives until the shape changes and nobody looks. // // Mutation caught: moving the `delete` back inside the recursive walk. func TestTheTimestampExemptionDoesNotReachInsideTheLine(t *testing.T) { const line = `{"time":"2026-08-29T03:00:00.123456789+03:00","level":"INFO","msg":"config",` + `"nested":{"time":"7.50","key":"TM_PLATFORM_SIGNUP_GRANT_USD"}}` fields := loggedFields(t, line) var top, deep bool for _, f := range fields { if strings.Contains(f, "2026-08-29T03:00:00") { top = true // the handler's own clock leaked through } if f == "7.50" { deep = true // a value under a nested key called `time` was still looked at } } if top { t.Error("the handler's own timestamp reached the assertion: the false positive is back") } if !deep { t.Error("a value nested under a key called `time` was exempted from the check: the exemption " + "is meant to cover the handler's field, not any field that happens to share its name") } } // loggedFields is every key and every value the boot log printed, MINUS the handler's own timestamp. // // ⚠ It exists because the money test used to search the RAW buffer, and that buffer carries an // RFC3339Nano time whose digits collide with the very amounts it looks for. Measured on 200 000 // timestamps: `7.50` appears in 205 of them, `42500` in 5, `0.0425` in 3 — and that is PER LINE, // while a run prints one line per setting, so the collisions arrive in bursts (every line of one run // shares the same second). Reproduced at `-count=3000`. The test therefore went red over a fact that // does not exist: the value IS withheld — `config.go` records `valueAmount` for both, which this same // test asserts a few lines above. Register row PD-429. // // ⚠ A flake in a MONEY test is worse than no test, and that is why this is repaired rather than left // with a note: red that is usually noise trains the reader to skip it, and the day an amount really // leaks is the day that habit costs the most. // // ⚠ PARSED, not muted, and the reason is INDEPENDENCE FROM THE FORMAT rather than reach. Silencing // `time` through slog's ReplaceAttr would work today and break the day the handler, its options or // the time layout change — the search would still be over one rendered blob, and the next collision // would be someone else's afternoon. // // ⚠ What this does NOT buy, measured rather than assumed: it is not WIDER than the old form. The // raw-buffer search covered `msg` too — planted an amount into the message and both forms went red, // so "now it catches the amount in any field" would have been a pleasing sentence and a false one. // What changed is that the false POSITIVE is gone and the assertion no longer depends on how time is // rendered. Equal reach, no noise. func loggedFields(t *testing.T, log string) []string { t.Helper() var out []string var walk func(v any) walk = func(v any) { switch v := v.(type) { case map[string]any: for k, sub := range v { out = append(out, k) walk(sub) } case []any: for _, sub := range v { walk(sub) } case nil: default: out = append(out, fmt.Sprint(v)) } } for _, l := range strings.Split(strings.TrimSpace(log), "\n") { if l == "" { continue } var m map[string]any if err := json.Unmarshal([]byte(l), &m); err != nil { // Not a skip: a line this cannot read is a line nothing checks, and the whole point is // that every field is looked at. t.Fatalf("the boot log emitted a line that is not JSON: %q (%v)", l, err) } // ⚠ Dropped at the TOP LEVEL only, which is where the handler writes it. The first version of // this helper skipped any key named `time` at any DEPTH, and that is a hole pointing the wrong // way: a nested group called `time` would have been quietly exempt from a check whose whole // purpose is that nothing is exempt. Today every line is flat, so the two behaved alike — which // is exactly how such a hole survives to the day the shape changes. delete(m, slog.TimeKey) walk(m) } if len(out) == 0 { t.Fatal("the boot log yielded no fields at all: this assertion would pass over anything") } return out } // The completeness gate, and the reason it is written against the SOURCE rather than a list: a print // that covers most of the configuration is worse than none, because the variable an operator is // hunting is exactly the one nobody remembered to record. A setting added without a record fails // here, in the same commit that adds it. func TestEverySettingThisServiceReadsIsPrinted(t *testing.T) { src, err := os.ReadFile("config.go") if err != nil { t.Fatal(err) } want := map[string]bool{} for _, m := range regexp.MustCompile(`TM_PLATFORM_[A-Z0-9_]+`).FindAllString(string(src), -1) { // A *_FILE variable is the alternative SOURCE of the key beside it, not a setting of its own: // it is recorded as `source=file` on that key, and its path is never printed. want[strings.TrimSuffix(m, "_FILE")] = true } got, _ := effective(t) var missing []string for k := range want { if _, ok := got[k]; !ok { missing = append(missing, k) } } sort.Strings(missing) if len(missing) != 0 { t.Fatalf("read but never printed: %v", missing) } if len(got) != len(want) { t.Fatalf("%d settings printed, %d read from the environment", len(got), len(want)) } } // A DSN carries a password and a client secret is a client secret. What the operator gets is the // fact and the SOURCE, which is what answers "did my file get picked up". func TestASecretIsNamedAndNeverPrinted(t *testing.T) { path := t.TempDir() + "/dsn" if err := os.WriteFile(path, []byte("postgres://u:hunter2@db/x\n"), 0o600); err != nil { t.Fatal(err) } t.Setenv("TM_PLATFORM_DSN_FILE", path) // The whole OIDC quartet, because half a login configuration is refused at boot. t.Setenv("TM_PLATFORM_OIDC_CLIENT_SECRET", "s3cret") t.Setenv("TM_PLATFORM_OIDC_ISSUER", "https://accounts.example.org") t.Setenv("TM_PLATFORM_OIDC_CLIENT_ID", "client") t.Setenv("TM_PLATFORM_OIDC_REDIRECT_URL", "https://app.example.org/auth/callback") got, line := effective(t) if got["TM_PLATFORM_DSN"].Source != sourceFile || got["TM_PLATFORM_DSN"].Value != valueSet { t.Errorf("DSN printed as %+v", got["TM_PLATFORM_DSN"]) } if got["TM_PLATFORM_OIDC_CLIENT_SECRET"].Source != sourceEnv { t.Errorf("client secret printed as %+v", got["TM_PLATFORM_OIDC_CLIENT_SECRET"]) } for _, leaked := range []string{"hunter2", "s3cret", path} { if strings.Contains(line, leaked) { t.Errorf("the boot line carries %q", leaked) } } // And the value really was read, not lost by being redacted. c, err := Load() if err != nil { t.Fatal(err) } if !strings.Contains(c.DSN, "hunter2") || c.OIDCClientSecret != "s3cret" { t.Fatal("the secret was redacted out of the configuration itself, not only out of the log") } } // Money does not reach an INFO log in ANY form (D39.84, PD-99) — including the amounts an operator // configured. The source is what they need, and the source is all they get. func TestAConfiguredAmountIsNeverPrinted(t *testing.T) { t.Setenv("TM_PLATFORM_SIGNUP_GRANT_USD", "7.50") // ⚠ The per-chapter rate used to stand beside the grant here and is GONE: what a chapter costs is // no longer a setting at all (see TestThePerChapterRateIsNoLongerAConfigurationAtAll). What // replaced it, the hold factor, is a PERCENTAGE and not an amount — so it is logged plainly, and // the assertion below is about the money that is left. got, line := effective(t) for _, k := range []string{"TM_PLATFORM_SIGNUP_GRANT_USD"} { if got[k].Source != sourceEnv { t.Errorf("%s: source %q, want the environment it came from", k, got[k].Source) } if got[k].Value != valueAmount { t.Errorf("%s: value %q, want it withheld", k, got[k].Value) } } fields := loggedFields(t, line) // parsed ONCE: the log does not change between amounts for _, amount := range []string{"7.50", "7500000"} { for _, field := range fields { if strings.Contains(field, amount) { t.Errorf("the boot line carries the amount %q, in the field %q", amount, field) } } } c, err := Load() if err != nil { t.Fatal(err) } if c.SignupGrantMicroUSD != 7_500_000 { t.Fatalf("the amount did not apply: %d", c.SignupGrantMicroUSD) } } // A default and an environment variable holding the same string are the same line without the // source, and telling them apart is the whole question ("did my setting take effect"). func TestTheSourceOfEveryValueIsPrintedBesideIt(t *testing.T) { t.Setenv("TM_PLATFORM_ADDR", "127.0.0.1:8080") // exactly the default got, line := effective(t) if got["TM_PLATFORM_ADDR"].Source != sourceEnv { t.Errorf("a value set to the default reads as %q", got["TM_PLATFORM_ADDR"].Source) } if got["TM_PLATFORM_AFTER_LOGIN"].Source != sourceDefault { t.Errorf("an unset value reads as %q", got["TM_PLATFORM_AFTER_LOGIN"].Source) } // One line per setting, as structured fields rather than a rendered sentence. var first map[string]any if err := json.Unmarshal([]byte(strings.SplitN(line, "\n", 2)[0]), &first); err != nil { t.Fatal(err) } for _, k := range []string{"key", "value", "source"} { if _, ok := first[k]; !ok { t.Errorf("the boot line has no %q field: %v", k, first) } } } // An intake root that is not absolute names a different directory to every process that reads a // book's workdir out of the database — the same class as PD-149 on the state directory, refused in // the same place. func TestARelativeBooksDirectoryIsRefusedAtBoot(t *testing.T) { t.Setenv("TM_PLATFORM_LANGUAGE_PAIRS", "zh>ru") t.Setenv("TM_PLATFORM_BOOKS_DIR", "books") if _, err := Load(); err == nil { t.Fatal("a relative TM_PLATFORM_BOOKS_DIR was accepted") } t.Setenv("TM_PLATFORM_BOOKS_DIR", "/srv/tm/books") c, err := Load() if err != nil { t.Fatal(err) } if c.IntakeEnabled() { t.Fatal("intake is enabled without an engine binary: a deployment that cannot parse would take uploads it can only reject") } t.Setenv("TM_PLATFORM_ENGINE_BIN", "/opt/engine/2026.08.01/tmctl") if c, err = Load(); err != nil || !c.IntakeEnabled() { t.Fatalf("intake stayed off with a root and an engine: %v %+v", err, c.Intake) } } // The upload deadline is bounded by THREE windows, and a deployment that breaks any of them loses // something: past the intake sweep's grace the sweep deletes the row and the directory out from under // a request still writing into them; past the parse claim's grace the sweep claims the parse of a // book whose own upload is still walking, and the two race for it; past the idempotency claim window // a retry takes the claim from an upload that is still running and the user gets a second book. // // ⚠ The boundary is pinned to the EXACT value. What this test does NOT do — said here because an // earlier edition of this very comment claimed it did — is tell the three windows apart: the parse // claim's grace is the tightest of them today (20 minutes against 30 and 60), so every case below is // refused by that one term, and deleting either of the other two from the gate leaves this test // green. Their coverage lives in TestEveryWindowAnUploadMustFitInsideIsActuallyConsulted, which can // make each of them tightest in turn because it does not have to use the real constants. And "under // the window" is not enough either: what an upload does AFTER its body has to fit as well. func TestAnUploadDeadlineIsRefusedUnlessTheWholeUploadFitsTheTightestWindow(t *testing.T) { t.Setenv("TM_PLATFORM_LANGUAGE_PAIRS", "zh>ru") t.Setenv("TM_PLATFORM_BOOKS_DIR", "/srv/tm/books") t.Setenv("TM_PLATFORM_ENGINE_BIN", "/opt/engine/2026.08.01/tmctl") window := min(books.UploadGrace, books.ClaimGrace, pgstore.ClaimStale) for _, c := range []struct { window string deadline time.Duration }{ {"the intake sweep's grace", books.UploadGrace + time.Minute}, {"the idempotency claim window", pgstore.ClaimStale + time.Minute}, // The case that matters for what this pack changed, even though the term above would also // catch it: at 21 minutes the sweep sees a book whose `added_at` — the stamp it falls back to, // written before the body arrived — is older than the claim's grace, while the upload that // created it is still walking. {"the parse claim's grace", books.ClaimGrace + time.Minute}, // Inside the tightest window and still wrong: the body ends a second before the claim is // stealable, and everything the upload does afterwards happens after that. {"the tightest window, by one second", window - time.Second}, {"the tightest window, once the tail is counted", window - books.UploadSettle}, } { t.Setenv("TM_PLATFORM_UPLOAD_DEADLINE", c.deadline.String()) if _, err := Load(); err == nil { t.Errorf("an upload deadline of %s was accepted, though the whole upload does not fit %s (tightest: %s)", c.deadline, c.window, window) } } t.Setenv("TM_PLATFORM_UPLOAD_DEADLINE", (window - books.UploadSettle - time.Second).String()) if _, err := Load(); err != nil { t.Fatalf("a deadline whose whole upload fits the window was refused: %v", err) } } // The cap on concurrent cuts is a number an operator sets, it defaults to the figure the host was // sized for, and zero is refused rather than taken as "no limit" — an intake that cuts nothing would // accept every book `parsing` and look like it is working. // // ⚠ The refusal comes from the READER of counts (`loader.number`, which rejects every non-positive // value), not from a check of the intake's own. An earlier edition of this pack added a second check // beside it and this test appeared to pin it; it could not, because the first refusal fires and the // second line is unreachable. The behaviour is what is pinned here, not the layer. func TestTheCapOnConcurrentCutsIsConfiguredAndRefusesAnEmptyOne(t *testing.T) { t.Setenv("TM_PLATFORM_LANGUAGE_PAIRS", "zh>ru") t.Setenv("TM_PLATFORM_BOOKS_DIR", "/srv/tm/books") t.Setenv("TM_PLATFORM_ENGINE_BIN", "/opt/engine/2026.08.01/tmctl") c, err := Load() if err != nil { t.Fatalf("the default configuration was refused: %v", err) } if c.Intake.MaxCuts != books.DefaultMaxCuts { t.Errorf("an unset cap gave %d cuts, want the package default %d", c.Intake.MaxCuts, books.DefaultMaxCuts) } // Not the default, and not any other number in this deployment: a value the operator chose has to // arrive as the value the operator chose. t.Setenv("TM_PLATFORM_MAX_CUTS", "7") if c, err = Load(); err != nil || c.Intake.MaxCuts != 7 { t.Fatalf("a configured cap of 7 gave %d (err %v)", c.Intake.MaxCuts, err) } for _, v := range []string{"0", "-1"} { t.Setenv("TM_PLATFORM_MAX_CUTS", v) if _, err := Load(); err == nil { t.Errorf("a cap of %s was accepted, so this deployment would cut nothing and say nothing", v) } } } // An intake with no declared pairs makes the wire and the intake say opposite things. func TestAnIntakeWithNoDeclaredPairsIsRefusedAtBoot(t *testing.T) { t.Setenv("TM_PLATFORM_BOOKS_DIR", "/srv/tm/books") t.Setenv("TM_PLATFORM_ENGINE_BIN", "/opt/engine/2026.08.01/tmctl") if _, err := Load(); err == nil { t.Fatal("an intake that declares no language pair was accepted") } // A list that declares only pairs it CANNOT run is the same emptiness one level down: the intake // judges by the available half, so it would accept every book while `/capabilities` lists nothing // translatable. t.Setenv("TM_PLATFORM_LANGUAGE_PAIRS", "ja>ru:unavailable") if _, err := Load(); err == nil { t.Fatal("an intake whose every declared pair is unavailable was accepted") } t.Setenv("TM_PLATFORM_LANGUAGE_PAIRS", "zh>ru,ja>ru:unavailable") if _, err := Load(); err != nil { t.Fatalf("a declared available pair was refused: %v", err) } } // Every window the gate is supposed to weigh is actually weighed. // // Built from values rather than from the real constants, and that is the whole point: with the real // ones the tightest window hides the other two, so a term deleted from the choice changes nothing any // boot-level case can observe (see the comment above). Here each window is made the tightest in turn, // with the other two far enough away that only the intended one can produce the answer. func TestEveryWindowAnUploadMustFitInsideIsActuallyConsulted(t *testing.T) { const tight, loose, looser = 5 * time.Minute, time.Hour, 2 * time.Hour for _, c := range []struct { window string sweepGrace, parseClaim, idempotencyKey time.Duration }{ {"the intake sweep's grace", tight, loose, looser}, {"the parse claim's grace", loose, tight, looser}, {"the idempotency claim window", loose, looser, tight}, } { if got := intakeWindow(c.sweepGrace, c.parseClaim, c.idempotencyKey); got != tight { t.Errorf("with %s the tightest at %s, the gate would hold an upload to %s: that window is not consulted at all", c.window, tight, got) } } }