2026-09-04 19:21:08 +02:00
|
|
|
|
---
|
|
|
|
|
|
id: FT-WP-0002
|
|
|
|
|
|
type: workplan
|
|
|
|
|
|
title: "Provision the Telegram presence from a declared specification"
|
|
|
|
|
|
domain: infotech
|
|
|
|
|
|
repo: fluid-telegram
|
|
|
|
|
|
status: proposed
|
|
|
|
|
|
owner: worsch
|
|
|
|
|
|
topic_slug: fluid-telegram
|
|
|
|
|
|
created: "2026-09-04"
|
|
|
|
|
|
updated: "2026-09-04"
|
|
|
|
|
|
planning_priority: high
|
|
|
|
|
|
planning_order: 2
|
|
|
|
|
|
depends_on:
|
|
|
|
|
|
- FT-WP-0001
|
|
|
|
|
|
related_repos:
|
|
|
|
|
|
- pr-hall-of-helix
|
|
|
|
|
|
- helix-forge
|
2026-09-04 19:21:12 +02:00
|
|
|
|
state_hub_workstream_id: "f2373858-c932-5a4d-81b6-db9a3595135a"
|
2026-09-04 19:21:08 +02:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
# FT-WP-0002 — Declared presence provisioning
|
|
|
|
|
|
|
|
|
|
|
|
Replace the manual route of `FT-WP-0001` T01–T03 with a specification and a
|
|
|
|
|
|
reconciler. The design, its constraints and its guardrails are in
|
|
|
|
|
|
`docs/provisioning.md`; this workplan implements it.
|
|
|
|
|
|
|
|
|
|
|
|
The shape of the problem, stated once: the Bot API cannot create a bot or a
|
|
|
|
|
|
channel. Both are client capabilities reachable only through MTProto with a user
|
|
|
|
|
|
account (Canon INT-03). So provisioning is an MTProto client acting as a
|
|
|
|
|
|
designated operator, and three things stay human — a phone number, the
|
|
|
|
|
|
`api_id`/`api_hash` web form, and the login code that mints the session. That is
|
|
|
|
|
|
one bounded bootstrap, after which the surface is declared and converges.
|
|
|
|
|
|
|
|
|
|
|
|
## T01 — Bootstrap the operator account and session
|
|
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
|
id: FT-WP-0002-T01
|
|
|
|
|
|
status: todo
|
|
|
|
|
|
priority: high
|
2026-09-04 19:21:12 +02:00
|
|
|
|
state_hub_task_id: "039e5358-07c1-5b01-a186-5ece9aab75e4"
|
2026-09-04 19:21:08 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**The irreducible human step.** Register a dedicated Telegram account for the
|
|
|
|
|
|
operator — never a person's own, so that a rate limit costs nothing personal.
|
|
|
|
|
|
Obtain `api_id` / `api_hash` from my.telegram.org.
|
|
|
|
|
|
|
|
|
|
|
|
Then `provision session bootstrap` runs the interactive login once and writes the
|
|
|
|
|
|
session string to OpenBao. The command is the only interactive one in the tool,
|
|
|
|
|
|
and it exists so that the interactive part is bounded and named rather than
|
|
|
|
|
|
spread through the process.
|
|
|
|
|
|
|
|
|
|
|
|
Nothing else in this workplan is interactive.
|
|
|
|
|
|
|
|
|
|
|
|
## T02 — Publish the presence schema and the campaign's spec
|
|
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
|
id: FT-WP-0002-T02
|
|
|
|
|
|
status: todo
|
|
|
|
|
|
priority: high
|
2026-09-04 19:21:12 +02:00
|
|
|
|
state_hub_task_id: "01684264-37a5-5fe8-a491-37686e094428"
|
2026-09-04 19:21:08 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
The schema is in `presence/telegram.schema.yaml` with a worked example beside it.
|
|
|
|
|
|
What remains is the real instance at `pr-hall-of-helix/presence/telegram.yaml`,
|
|
|
|
|
|
plus the avatar asset.
|
|
|
|
|
|
|
|
|
|
|
|
The spec is editorial — a bot's name, its description, the channel titles a
|
|
|
|
|
|
stranger reads — so it belongs to the campaign. The provisioner takes a spec path
|
|
|
|
|
|
as input, which is what keeps this interface reusable by campaigns other than the
|
|
|
|
|
|
Hall of Helix.
|
|
|
|
|
|
|
|
|
|
|
|
Blocked on `pr-hall-of-helix` existing.
|
|
|
|
|
|
|
|
|
|
|
|
## T03 — Implement `provision plan`
|
|
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
|
id: FT-WP-0002-T03
|
|
|
|
|
|
status: todo
|
|
|
|
|
|
priority: high
|
2026-09-04 19:21:12 +02:00
|
|
|
|
state_hub_task_id: "c3923422-7f2e-5bf9-906c-6d59572b09c0"
|
2026-09-04 19:21:08 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Read the spec, validate it against the schema, read live state through MTProto,
|
|
|
|
|
|
read `presence/resolved/<campaign>.yaml`, print a diff. Write nothing.
|
|
|
|
|
|
|
|
|
|
|
|
Validation is where the rights clamp lives: an `admin_rights` entry other than
|
|
|
|
|
|
`post_messages` fails here, loudly, rather than being quietly dropped. The intent
|
|
|
|
|
|
forbids the system from holding a right it may not exercise, and a spec is the
|
|
|
|
|
|
cheapest place to catch someone asking for one.
|
|
|
|
|
|
|
|
|
|
|
|
`plan` must be honest about what it cannot see. A username's availability is not
|
|
|
|
|
|
knowable without attempting it, so the plan says "will attempt, with fallbacks"
|
|
|
|
|
|
rather than promising an outcome.
|
|
|
|
|
|
|
|
|
|
|
|
## T04 — Implement `provision apply`
|
|
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
|
id: FT-WP-0002-T04
|
|
|
|
|
|
status: todo
|
|
|
|
|
|
priority: high
|
2026-09-04 19:21:12 +02:00
|
|
|
|
state_hub_task_id: "5659b72a-8309-58b6-96e9-79137ce41ed4"
|
2026-09-04 19:21:08 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Execute an approved plan. Idempotent, keyed on resolved ids rather than names.
|
|
|
|
|
|
|
|
|
|
|
|
- Bot: BotFather `/newbot`, then `/setabouttext`, `/setdescription`, `/setuserpic`.
|
|
|
|
|
|
Parse the token from the reply, write it straight to OpenBao, and hold it in
|
|
|
|
|
|
memory nowhere longer than that call. Walk `username_preference` on collision.
|
|
|
|
|
|
- Channels: `channels.createChannel`, test channel first. Set the public
|
|
|
|
|
|
username; a private channel that declares one is a validation error, not a
|
|
|
|
|
|
silent skip.
|
|
|
|
|
|
- Bot as administrator: `channels.editAdmin` with `post_messages` and nothing
|
|
|
|
|
|
else.
|
|
|
|
|
|
- Write `presence/resolved/<campaign>.yaml` with the spec digest.
|
|
|
|
|
|
|
|
|
|
|
|
Three refusals carry the weight, and each is a real failure mode rather than a
|
|
|
|
|
|
hypothetical: there is **no destroy path** — removing a channel from the spec
|
|
|
|
|
|
warns, because deleting one destroys its subscribers and history irreversibly;
|
|
|
|
|
|
**drift on a destructive field stops the run** — a demoted bot or a taken-over
|
|
|
|
|
|
username exits non-zero rather than being "fixed" by a tool that does not
|
|
|
|
|
|
understand what happened; and **the public channel is not touched** until the
|
|
|
|
|
|
test channel has recorded a successful publication.
|
|
|
|
|
|
|
|
|
|
|
|
## T05 — Provision the redaction salt
|
|
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
|
id: FT-WP-0002-T05
|
|
|
|
|
|
status: todo
|
|
|
|
|
|
priority: high
|
2026-09-04 19:21:12 +02:00
|
|
|
|
state_hub_task_id: "d5fd905a-6abd-5ae1-9105-abc4c2a7127e"
|
2026-09-04 19:21:08 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Generate 32 bytes if and only if the OpenBao path is empty, and never overwrite.
|
|
|
|
|
|
Supersedes `FT-WP-0001` T03.
|
|
|
|
|
|
|
|
|
|
|
|
The create-if-absent rule is not defensiveness. `docs/observation.md` explains
|
|
|
|
|
|
that rotating the salt silently invalidates every longitudinal comparison the
|
|
|
|
|
|
interface has made, and does so with no visible failure — the numbers keep
|
|
|
|
|
|
arriving and quietly stop meaning what they used to. A tool that can rewrite it
|
|
|
|
|
|
is a tool that will eventually rewrite it, so the only path to a new salt is a
|
|
|
|
|
|
deliberate human write, which is a decision rather than a run.
|
|
|
|
|
|
|
|
|
|
|
|
## T06 — Derive adapter configuration from the resolved state
|
|
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
|
id: FT-WP-0002-T06
|
|
|
|
|
|
status: todo
|
|
|
|
|
|
priority: medium
|
2026-09-04 19:21:12 +02:00
|
|
|
|
state_hub_task_id: "c305b83f-b05e-5f09-9182-9896ae137c38"
|
2026-09-04 19:21:08 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
The adapter reads its chat id and bot token from the resolved state and OpenBao.
|
|
|
|
|
|
It gets no MTProto session and no provisioning code path, so a compromised
|
|
|
|
|
|
adapter cannot create, rename, delete or re-permission anything. That separation
|
|
|
|
|
|
is the point of running two planes rather than one tool with a flag.
|
|
|
|
|
|
|
|
|
|
|
|
## T07 — Drift check
|
|
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
|
id: FT-WP-0002-T07
|
|
|
|
|
|
status: todo
|
|
|
|
|
|
priority: low
|
2026-09-04 19:21:12 +02:00
|
|
|
|
state_hub_task_id: "f3f36415-ed55-5717-b752-706b04ba5458"
|
2026-09-04 19:21:08 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`provision plan --check` exits non-zero on drift, suitable for a scheduled run.
|
|
|
|
|
|
Report only; never auto-apply. An unattended reconciler that can change a public
|
|
|
|
|
|
channel is the thing IEI-1 §7 exists to prevent.
|
|
|
|
|
|
|
|
|
|
|
|
## T08 — Settle the §7 reading
|
|
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
|
id: FT-WP-0002-T08
|
|
|
|
|
|
status: todo
|
|
|
|
|
|
priority: medium
|
2026-09-04 19:21:12 +02:00
|
|
|
|
state_hub_task_id: "864bcd23-6357-5460-a8e6-98ec9a929e29"
|
2026-09-04 19:21:08 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`InterfaceEvolutionIntent.md` §7 lists channel and bot creation under explicit
|
|
|
|
|
|
non-authority without distinguishing the Daimon from an operator-run tool. The
|
|
|
|
|
|
intended reading is that §7 constrains *autonomous* action and an approved plan
|
|
|
|
|
|
is not autonomous — but that is currently a reading rather than something the
|
|
|
|
|
|
document says, and a constitutional document should not depend on one.
|
|
|
|
|
|
|
|
|
|
|
|
Add a one-sentence clarification, as a governance change under §20, before T04
|
|
|
|
|
|
runs against anything real.
|