Ground the repo docs and move presence provisioning to a declared spec

Align AGENTS.md with the FT-WP- prefix the hub already registered, and
rewrite INTENT.md and SCOPE.md, which were still the generated stubs and
carried none of the composed-post framing the interface is built around.

Fix two artifact inconsistencies: R-1's retry comment still described
POST /v1/hall-entries idempotent on entry_id, from before the reframe to
composed posts; and the seed pressure record listed only one of the two
hypotheses that cite it. Rename it to match its id.

Replace the manual T01-T03 route with FT-WP-0002. The Bot API cannot
create a bot or a channel, so provisioning is an MTProto client acting as
a designated operator account (Canon INT-03), driven from a declared
presence spec owned by the campaign. One bounded human bootstrap remains
and docs/provisioning.md says why it cannot be removed.

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 19:21:08 +02:00
parent 3dae4ae74c
commit 5a785b9d9f
11 changed files with 670 additions and 46 deletions

View file

@ -46,6 +46,14 @@ first experiment. See `docs/` and the handover package at
What remains needs either a person with a Telegram account or a running
adapter.
**T01T03 have moved.** They were written as manual steps; they are now
provisioned from a declared specification by `FT-WP-0002`, and the three tasks
below are kept as the record of what must be true rather than as instructions to
follow. The Bot API cannot create a bot or a channel, so provisioning is an
MTProto client acting as a designated operator account (Canon INT-03); one
bounded human bootstrap remains, and `docs/provisioning.md` says exactly what it
is and why it cannot be removed.
## T01 — Register the bot
```task
@ -55,14 +63,18 @@ priority: high
state_hub_task_id: "a5449e86-3181-54b6-a0fe-e5c761b3e4ba"
```
**Human step.** Register the HelixForge bot through BotFather (Canon BOT-01).
Bots are first-class Telegram accounts and need no phone number.
**Superseded by `FT-WP-0002` T04.** The bot is registered by the provisioner
from the declared presence spec, not by hand: BotFather `/newbot` driven over
MTProto, name, about text, description and avatar set from the spec, and the
token written straight to OpenBao without passing through a terminal. Consistent
with `HF-WP-0002` (bao.coulomb.social).
Set the bot's name, description and profile image to something a reader
encountering it cold would recognise as HelixForge.
What survives from the original step is the requirement, not the method: a
reader encountering the bot cold should recognise it as HelixForge. That is now
`bot.name` / `bot.about` / `bot.description` in the spec, where it is reviewable
before it is applied.
Write the token to OpenBao immediately; do not paste it anywhere else, not even
briefly. Consistent with `HF-WP-0002` (bao.coulomb.social).
Close this task when `FT-WP-0002` T04 has run. See `docs/provisioning.md`.
## T02 — Create the channels
@ -73,13 +85,21 @@ priority: high
state_hub_task_id: "597ab1b7-5903-5a5d-a510-0a6946325404"
```
**Human step.** Create the **private test channel first**, then the public
channel (Canon PUB-01). Add the bot as administrator with **Post Messages**
only — the intent forbids it from using any wider right, so granting one would
be a permission the system is not allowed to exercise.
**Superseded by `FT-WP-0002` T04.** Both channels are created by the
provisioner via `channels.createChannel`, private test channel first (Canon
PUB-01), with the bot added as administrator through `channels.editAdmin`.
Every verification below runs against the private channel. Nothing reaches the
public channel until the rendering has been seen by a person.
The ordering and the rights are no longer conventions a person is trusted to
follow. `apply` will not touch the public channel until the test channel has
recorded a successful publication, and it grants **Post Messages** and refuses
anything wider *even if the spec asks for it* — the intent forbids the system
from holding a right it may not exercise, so the grant is the right place to
enforce that.
Every verification below still runs against the private channel, and nothing
reaches the public channel until a person has looked at a rendering.
Close this task when `FT-WP-0002` T04 has run.
## T03 — Generate and store the redaction salt
@ -90,9 +110,15 @@ priority: high
state_hub_task_id: "bbe5da73-e604-5dfe-b25f-fba802f67618"
```
`openssl rand -hex 32`, stored in OpenBao beside the token, injected as
`FLUID_REDACTION_SALT`. It must never change: see `docs/observation.md` for why
rotating it silently invalidates every longitudinal comparison.
**Superseded by `FT-WP-0002` T05.** The provisioner generates 32 bytes if and
only if the OpenBao path is empty, and has no path that overwrites one. Injected
as `FLUID_REDACTION_SALT`.
It must never change: see `docs/observation.md` for why rotating it silently
invalidates every longitudinal comparison. Making that a property of the tool
rather than a note in a workplan is the whole reason the step moved.
Close this task when `FT-WP-0002` T05 has run.
## T04 — Record the intent and publish R-1

View file

@ -0,0 +1,178 @@
---
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
---
# FT-WP-0002 — Declared presence provisioning
Replace the manual route of `FT-WP-0001` T01T03 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
```
**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
```
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
```
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
```
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
```
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
```
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
```
`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
```
`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.