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:
parent
3dae4ae74c
commit
5a785b9d9f
11 changed files with 670 additions and 46 deletions
|
|
@ -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.
|
||||
|
||||
**T01–T03 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
|
||||
|
||||
|
|
|
|||
178
workplans/FT-WP-0002-declared-presence-provisioning.md
Normal file
178
workplans/FT-WP-0002-declared-presence-provisioning.md
Normal 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` 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
|
||||
```
|
||||
|
||||
**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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue