diff --git a/conformance/handover_test.go b/conformance/handover_test.go deleted file mode 100644 index 74240d7..0000000 --- a/conformance/handover_test.go +++ /dev/null @@ -1,51 +0,0 @@ -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)) - } -} diff --git a/docs/handover/fluid-telegram.md b/docs/handover/fluid-telegram.md deleted file mode 100644 index 8f0c2bd..0000000 --- a/docs/handover/fluid-telegram.md +++ /dev/null @@ -1,173 +0,0 @@ -# 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 -revision–experiment–fitness 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. diff --git a/workplans/FLUID-WP-0008-fluid-telegram-handover.md b/workplans/FLUID-WP-0008-fluid-telegram-handover.md index 918e8d4..28090b0 100644 --- a/workplans/FLUID-WP-0008-fluid-telegram-handover.md +++ b/workplans/FLUID-WP-0008-fluid-telegram-handover.md @@ -34,7 +34,7 @@ in the `fluid-telegram` repository. ```task id: FLUID-WP-0008-T01 -status: done +status: todo 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 — BLOCKED ON A PERSON +## T02 - Bot identity ```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 — BLOCKED ON A PERSON +## T03 - Channels ```task id: FLUID-WP-0008-T03 @@ -77,7 +77,7 @@ published publicly. ```task id: FLUID-WP-0008-T04 -status: done +status: todo 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: done +status: todo priority: high state_hub_task_id: "24804694-1581-5d83-a5d4-db46e5af5070" ``` @@ -103,7 +103,7 @@ error mapping. ```task id: FLUID-WP-0008-T06 -status: done +status: todo 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: done +status: todo 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 — BLOCKED ON T02, T03 AND SUBSCRIBERS +## T08 - Live experiment and audit ```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: done +status: todo priority: medium state_hub_task_id: "be0da67f-67d2-5c0d-bd19-0235310b053e" ```