Make the plan honest and the runbook's paths real

Two things that would have gone wrong on the first live run.

The plan promised "create bot.avatar" and apply had no avatar code at all:
/setuserpic needs a photo upload that is not implemented. A plan that
promises an action apply silently skips is worse than one that admits the
gap, because it makes every other line less trustworthy. The avatar is now
deferred, with instructions for setting it by hand.

The runbook wrote secrets to "<interface-path>/telegram/...", which is not
what the code computes. An operator following it would have put api_id and
api_hash somewhere the tool never looks, and found out at the first
connection attempt. Both documents now carry the real paths --
<mount>/fluid-telegram/<campaign>/telegram/<key> -- with a copy-pasteable
bao kv put for the one secret written by hand, and a verify step before
the session is minted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172sgCZEEDJcnQmr4SGDvKa

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1361245@bnt-lap001
Assistant-Session: b3b428ef-f3e6-4688-b091-01f71461d66a
This commit is contained in:
tegwick 2026-09-04 22:02:56 +02:00
parent 5ddfee8250
commit 014ee5c746
4 changed files with 90 additions and 18 deletions

View file

@ -149,7 +149,14 @@ func planBot(p *Plan, sp *spec.Presence, rs *state.Resolved, live Live) error {
sp.Bot.Name, len(sp.Bot.UsernamePreference), strings.Join(sp.Bot.UsernamePreference, ", ")))
p.add(Create, "bot.profile", "set name, about text and description")
if sp.Bot.Avatar != "" {
p.add(Create, "bot.avatar", sp.Bot.Avatar)
// apply does not set the avatar yet: /setuserpic needs a photo
// upload, which is FT-WP-0002 T04's remaining piece. Say so, rather
// than promising an action that would be silently skipped -- a plan
// nobody can trust line by line is not worth reading.
p.addWhy(Defer, "bot.avatar", sp.Bot.Avatar,
"Not applied yet: setting a bot's picture needs a photo upload, which "+
"is not implemented. Set it by hand in @BotFather with /setuserpic, "+
"or leave it until the upload lands.")
}
return nil
}

View file

@ -211,3 +211,23 @@ func TestVerifiedTestChannelReleasesPublic(t *testing.T) {
t.Errorf("public should no longer be deferred:\n%s", p.Render())
}
}
// The plan must not promise an action apply will silently skip. The avatar is
// the live case: /setuserpic needs an upload that is not implemented, so the
// plan defers it with an explanation instead of claiming a create.
func TestAvatarIsDeferredNotPromised(t *testing.T) {
s := sp()
s.Bot.Avatar = "presence/assets/avatar.png"
p, _ := Compute(s, "sha256:x", &state.Resolved{}, fake{})
if find(p, Create, "bot.avatar") != nil {
t.Fatal("plan promised an avatar create that apply does not perform")
}
a := find(p, Defer, "bot.avatar")
if a == nil {
t.Fatalf("expected the avatar to be deferred:\n%s", p.Render())
}
if !strings.Contains(a.Why, "setuserpic") {
t.Errorf("deferral should say how to do it by hand: %q", a.Why)
}
}