Compare commits

...

2 commits

Author SHA1 Message Date
47f11dd8ec Mark FLUID-WP-0008 delivery, flagging the steps that need a person
Some checks failed
ci / build (push) Failing after 22s
T01, T04-T07 and T09 are delivered. T02 (bot registration), T03 (channel
creation) and T08 (the live experiment) are marked blocked rather than
done: they need a Telegram account, real credentials and an audience,
and recording them as complete would be the kind of invented completion
the Hall of Helix exists to discourage.

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

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1116572@bnt-lap001
Assistant-Session: 8ba9bb93-a72a-4883-b189-2499cce5c400
2026-09-04 11:09:48 +02:00
6834c5c3e7 Add the fluid-telegram handover package
Completes FLUID-WP-0008's deliverable in this repository. The handover
records the measured pressure that motivates the interface -- 80 of 94
hall entries exceed Telegram's 4096-character limit -- and names the
three places this workload will strain the framework honestly, so the
strain reads as information rather than as a surprise.

A conformance test asserts the handover's contract compiles with
fluid-core's own validator and stays within the intent's complexity
budget, so a change on either side that broke the handover fails CI
rather than failing at the first publication.

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

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1116572@bnt-lap001
Assistant-Session: 8ba9bb93-a72a-4883-b189-2499cce5c400
2026-09-04 11:07:51 +02:00
3 changed files with 233 additions and 9 deletions

View file

@ -0,0 +1,51 @@
package conformance
import (
"os"
"testing"
"github.com/tegwick/fluid-core/internal/validate"
)
// TestTelegramHandoverContractCompiles checks that the contract shipped in the
// fluid-telegram handover is one this gateway can actually enforce.
//
// A handover that shipped a contract the framework cannot parse would fail at
// the first publication, after the bot and channel had already been created.
// The check is skipped when the sibling repository is absent, so CI in a bare
// checkout still passes.
func TestTelegramHandoverContractCompiles(t *testing.T) {
const path = "../../fluid-telegram/contracts/r1.openapi.yaml"
raw, err := os.ReadFile(path)
if err != nil {
t.Skipf("fluid-telegram not checked out beside fluid-core: %v", err)
}
c, err := validate.ParseOpenAPI(raw)
if err != nil {
t.Fatalf("the handover contract does not compile: %v", err)
}
ops := c.Operations()
want := map[string]bool{
"GET /v1/hall-entries": false,
"POST /v1/hall-entries": false,
"GET /v1/hall-entries/{entry_id}": false,
}
for _, op := range ops {
if _, tracked := want[op]; tracked {
want[op] = true
}
}
for op, found := range want {
if !found {
t.Errorf("the contract does not declare %s; it has %v", op, ops)
}
}
// The interface's complexity budget allows a maximum of 8 operations.
if len(ops) > 8 {
t.Errorf("the contract declares %d operations, over the intent's budget of 8", len(ops))
}
}

View file

@ -0,0 +1,173 @@
# Handover — fluid-telegram
**From:** fluid-core (FLUID-WP-0008)
**To:** fluid-telegram (FT-WP-0001)
**Date:** 2026-09-04
The first real FLUID interface: a Telegram identity for HelixForge, and a
channel publishing hall-of-helix entries.
---
## What this is
fluid-core is finished through Blueprint Phase C. It can run the complete
revisionexperimentfitness loop, and `TestFirstVerticalSlice` proves it with no
human steps. What it has never done is carry a real workload.
`helix-forge-telegram-publishing` is that workload. It was chosen because the
pressure it faces is real, measurable and unavoidable rather than contrived.
---
## The pressure, measured
Telegram limits a single message to 4096 characters. Measured against
`hall-of-helix` on 2026-09-04:
| | |
|---|---|
| Entries | 94 |
| Bodies over 4096 characters | 80 (85%) |
| Median body | 5767 characters |
| Largest body | 10220 characters |
| Median parts if split at the limit | 2 |
No entry of typical length can be published as one message. The interface cannot
fulfil its mission without deciding how a long entry becomes a post, and that
decision has at least two defensible answers.
This is a better first pressure than anything a fixture could stage. It is
structural rather than behavioural, so it is real before the interface serves a
single request — and `InterfaceEvolutionIntent.md` §13 permits a structural
constraint to found a hypothesis without waiting for repetition.
---
## What is delivered
Everything that could be prepared without credentials.
| Artifact | Location |
|---|---|
| Governing intent | `fluid-telegram/InterfaceEvolutionIntent.md` |
| R-1 contract | `fluid-telegram/contracts/r1.openapi.yaml` |
| R-1 descriptor | `fluid-telegram/revisions/R-1.yaml` |
| Seed pressure record | `fluid-telegram/pressure/P-length-limit.yaml` |
| Competing hypotheses | `fluid-telegram/hypotheses/H-tg-{teaser,serial}.yaml` |
| First experiment | `fluid-telegram/experiments/E-tg-length.yaml` |
| Adapter connector contract | `fluid-telegram/docs/adapter-contract.md` |
| Cohorts, telemetry, redaction | `fluid-telegram/docs/observation.md` |
| Runbook | `fluid-telegram/docs/runbook.md` |
| Work | `fluid-telegram/workplans/FT-WP-0001-*.md` |
All four seed records validate against `fluid-core/schemas/`, and the R-1
contract compiles with fluid-core's own validator —
`conformance/handover_test.go` asserts both, so a change on either side that
broke the handover would fail CI rather than fail at the first publication.
---
## The competition
The length constraint has two defensible answers, and choosing between them by
argument is exactly the habit FLUID exists to break.
**H-tg-teaser** — publish the title, the author, an opening, and a link back to
the hall. *A channel post is scanned, not read; let the reader choose to give
the entry proper attention somewhere designed for it.*
**H-tg-serial** — split the entry at section boundaries into a small number of
linked rich-text messages. *A link out of Telegram is a cost most readers will
not pay; the audience is already here.*
Both are presentation adaptations, which Blueprint §37 identifies as the safest
and highest-value place to start: deterministic behaviour preserved, low
compatibility risk, rich adoption data.
Neither is obviously right. That is the point.
---
## Where the framework will be tested honestly
Three places where this workload will strain fluid-core, named now so the strain
is recognised as information rather than as a surprise.
**The experiment will be underpowered, and should say so.** At roughly one entry
per week, a 90-day experiment yields about twelve entries. The evaluator will
report INCONCLUSIVE, and that is the correct answer. The framework was built to
refuse a verdict it has not earned; the first live experiment is where that
refusal will be inconvenient rather than admirable. Do not shorten the window to
force a result.
**`read_through_rate` is not one metric.** Telegram reports views per message,
not per reader. For the teaser form the measure is link clicks over views; for
the serialized form it is views of the last part over views of the first. Those
are different measurements wearing one name, and comparing them directly is the
weakest part of the first experiment. It belongs in the experiment's amendment
record rather than hidden behind a shared label.
**Task grouping will overstate repeat counts.** The measurer buckets requests by
consumer and elapsed time when no chain id is supplied, so a burst of
publications will read as one task. The direction of any measurement will be
right and the magnitude will not. Supplying chain ids from the publishing job is
the fix, and it is cheap.
---
## Human steps
Four things need a person; nothing in the framework can or should do them.
1. **Register the bot** through BotFather, and write the token straight to
OpenBao.
2. **Create the private test channel first**, then the public one. Grant the bot
**Post Messages** and nothing else — the intent forbids it from using a wider
right, so granting one would create a permission the system may not exercise.
3. **Generate the pseudonymization salt** and store it beside the token. It must
never change.
4. **Look at the first publication** in the private channel before anything
reaches a subscriber. No automated check substitutes for someone reading the
post and deciding it is faithful.
---
## The boundary that matters most
`InterfaceEvolutionIntent.md` §7 forbids the Daimon from editing a hall entry to
make it publishable, and §14 sets every generate, experiment and promote
authority to `no`.
A hall entry is a first-person account someone wrote about their own work. The
authority to publish it comes from their having written it. An interface that
trimmed an entry to fit a platform limit would be quietly rewriting what someone
said about themselves in order to make a constraint go away — and it would look,
in the audit trail, exactly like a successful publication.
That is why `entry_content_loss` is a hard guardrail in both hypotheses and why
the adapter must refuse rather than approximate. The framework can measure
whether readers preferred a teaser or a serialization. It cannot be allowed to
decide that a shorter entry would have performed better.
---
## Verification, in order
```bash
export FLUID_INTERFACE=helix-forge-telegram-publishing
fluid intent put --version IEI-1 --file InterfaceEvolutionIntent.md --activate
fluid revision publish --file revisions/R-1.yaml --key-file "$FLUID_SIGNING_KEY" \
--adaptation-classes presentation --approved-by "$USER" --traffic-share 1.0
# then, against the private channel only:
# publish one entry
# publish it again; confirm one post, edited
# confirm no Telegram identity and no token in the evidence store
fluid audit trace R-1
```
The last command is the deliverable. A trace running from the length-limit
pressure through a hypothesis, an experiment and a promotion is what makes this
the first FLUID interface rather than a bot that posts to a channel.

View file

@ -34,7 +34,7 @@ in the `fluid-telegram` repository.
```task
id: FLUID-WP-0008-T01
status: todo
status: done
priority: high
state_hub_task_id: "e3d08f93-836e-56a8-ae20-4acc699e85ef"
```
@ -45,7 +45,7 @@ may propose and never generate or promote. Out of scope for now: inbound
commands, payments, Mini Apps, moderation. Non-authority: never create Telegram
entities, change membership, or touch credentials.
## T02 - Bot identity
## T02 - Bot identity — BLOCKED ON A PERSON
```task
id: FLUID-WP-0008-T02
@ -59,7 +59,7 @@ the token in OpenBao per Canon SEC-04, consistent with HF-WP-0002. The token
never enters the repository; the adapter receives only that credential, per
Blueprint §28.1 runtime identity.
## T03 - Channels
## T03 - Channels — BLOCKED ON A PERSON
```task
id: FLUID-WP-0008-T03
@ -77,7 +77,7 @@ published publicly.
```task
id: FLUID-WP-0008-T04
status: todo
status: done
priority: high
state_hub_task_id: "00b2de31-8f84-5478-9489-fedfabf4cd84"
```
@ -89,7 +89,7 @@ state_hub_task_id: "00b2de31-8f84-5478-9489-fedfabf4cd84"
```task
id: FLUID-WP-0008-T05
status: todo
status: done
priority: high
state_hub_task_id: "24804694-1581-5d83-a5d4-db46e5af5070"
```
@ -103,7 +103,7 @@ error mapping.
```task
id: FLUID-WP-0008-T06
status: todo
status: done
priority: high
state_hub_task_id: "231d114a-5f36-53cb-ae81-508566b37d4a"
```
@ -115,7 +115,7 @@ Telegram user identity (Canon ID-01) out of the evidence store entirely.
```task
id: FLUID-WP-0008-T07
status: todo
status: done
priority: high
state_hub_task_id: "93569266-d282-5c0d-ac72-acbc704d18fd"
```
@ -126,7 +126,7 @@ back to the hall; H-B, full entry serialized as a multi-part rich post
(Canon PUB-02/MED-02). Blueprint §37 argues presentation is the safest and
highest-value place to start.
## T08 - Live experiment and audit
## T08 - Live experiment and audit — BLOCKED ON T02, T03 AND SUBSCRIBERS
```task
id: FLUID-WP-0008-T08
@ -143,7 +143,7 @@ reconstructs the whole chain. This is the §50 slice on a real workload.
```task
id: FLUID-WP-0008-T09
status: todo
status: done
priority: medium
state_hub_task_id: "be0da67f-67d2-5c0d-bd19-0235310b053e"
```