diff --git a/conformance/handover_test.go b/conformance/handover_test.go index 94e61d1..74240d7 100644 --- a/conformance/handover_test.go +++ b/conformance/handover_test.go @@ -29,9 +29,9 @@ func TestTelegramHandoverContractCompiles(t *testing.T) { ops := c.Operations() want := map[string]bool{ - "GET /v1/channel-posts": false, - "POST /v1/channel-posts": false, - "GET /v1/channel-posts/{post_id}": false, + "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 { diff --git a/docs/handover/fluid-telegram.md b/docs/handover/fluid-telegram.md index cddf28d..8f0c2bd 100644 --- a/docs/handover/fluid-telegram.md +++ b/docs/handover/fluid-telegram.md @@ -5,13 +5,7 @@ **Date:** 2026-09-04 The first real FLUID interface: a Telegram identity for HelixForge, and a -channel publishing composed posts about hall-of-helix work. - -**The channel does not republish entries.** A hall entry is a considered -first-person account written for a colleague reading a repository. A channel -post is short, personal, entertaining and written to travel. The post is -composed *from* the entry; the entry is its source and its check, never its -payload. +channel publishing hall-of-helix entries. --- @@ -28,8 +22,8 @@ pressure it faces is real, measurable and unavoidable rather than contrived. ## The pressure, measured -A hall entry and a channel post are written for different readers. Measured -against `hall-of-helix` on 2026-09-04: +Telegram limits a single message to 4096 characters. Measured against +`hall-of-helix` on 2026-09-04: | | | |---|---| @@ -39,38 +33,15 @@ against `hall-of-helix` on 2026-09-04: | Largest body | 10220 characters | | Median parts if split at the limit | 2 | -| Entries with a portrait | 72 (77%) | - -The length figures are the visible symptom. The mismatch is one of register and -audience, and it would remain if the limit were ten times larger: an entry -written under fixed headings for someone already invested does not become a -post that stops a stranger scrolling by being shorter. +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. -## Where composition sits, and why - -Composing a condensed, entertaining post is a probabilistic editorial task. It -happens **before** this interface is called, for two reasons: - -1. `ArchitectureBlueprint.md` §48.1 names an LLM in the mandatory request path - as an anti-pattern. An interface that composed on demand would be - non-deterministic at exactly the point where determinism is its whole claim. -2. A post speaks about a named person's work in HelixForge's voice. That - deserves a human read, and a review step inside the request path is a review - step nobody performs. - -So the contract takes a *composed post*: body text already within the limit, -a cited source entry, a named reviewer, and a consent basis. A post carrying -none of those is refused with 403. - -This also draws the line that `pr-hall-of-helix` will later be extracted along. -The campaign owns what to say and when; this interface owns delivery and -measurement. - --- ## What is delivered @@ -82,9 +53,9 @@ Everything that could be prepared without credentials. | 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-register-mismatch.yaml` | -| Competing hypotheses | `fluid-telegram/hypotheses/H-tg-{visual,textonly}.yaml` | -| First experiment | `fluid-telegram/experiments/E-tg-delivery.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` | @@ -99,26 +70,22 @@ broke the handover would fail CI rather than fail at the first publication. ## The competition -This interface's first experiment is about **delivery**, which is what it owns. +The length constraint has two defensible answers, and choosing between them by +argument is exactly the habit FLUID exists to break. -**H-tg-visual** — attach the portrait; publish as a photo with the post as its -caption. *Reach in a scanned feed is won by stopping the scroll, and 72 of the -portraits already exist.* +**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-textonly** — publish as text with a source link, no visual. *Telegram -caps a caption at 1024 characters against 4096 for text. The scarce resource in -a condensed post is room to say something specific, and a generated portrait is -interchangeable in a way that a concrete detail is not.* +**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.* -They cannot both be right, which is what makes the pair worth running. Both are -presentation adaptations — Blueprint §37's safest and highest-value place to -start. +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. -**Composition competitions are the campaign's, not this interface's.** Every -post carries an opaque `variant` label, and the interface reports engagement -grouped by it. That is the whole of its involvement: it does not know what -`variant: warm-specific-v2` means and must not acquire an opinion. The campaign -runs its own FLUID loop over its own artifacts using that data. +Neither is obviously right. That is the point. --- @@ -134,11 +101,12 @@ 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. -**`engagement_rate` is a proxy, and a noisy one.** Telegram reports views, -reactions and forwards per message, not per reader, so the denominator is -subscribers rather than people who saw the post. At small subscriber counts -that is weak. It belongs in the experiment's amendment record rather than -hidden behind a confident-looking number. +**`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 @@ -167,25 +135,20 @@ Four things need a person; nothing in the framework can or should do them. ## The boundary that matters most -`InterfaceEvolutionIntent.md` §7 forbids the interface from composing, -rewriting, shortening or embellishing post text, and §14 sets every generate, -experiment and promote authority to `no`. +`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 post is a condensation written in HelixForge's voice about a named person's -work. It can flatter, it can misstate, and it can put a claim in someone's mouth -that their own entry never made. In the audit trail an unfaithful post looks -exactly like a faithful one. +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. -The defence is not that the composer is careful. It is structural: a human read -the post, the post names the entry it came from, the subject's consent has a -recorded basis, and all three are stored before anything reaches a subscriber. -`unreviewed_publication`, `missing_source_attribution` and -`missing_subject_consent` are hard guardrails, and a post carrying none of them -is refused rather than logged. - -Ninety-four entries already exist, written by people who took a seat in the hall -without anyone mentioning a Telegram channel. `FT-WP-0001` T07 exists to settle -that before T10 opens the public channel. Absence of objection is not consent. +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. --- @@ -205,17 +168,6 @@ fluid revision publish --file revisions/R-1.yaml --key-file "$FLUID_SIGNING_KEY" fluid audit trace R-1 ``` -The last command is the deliverable. A trace running from the register mismatch -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. - -## What comes after - -`FT-WP-0001` T13 extracts the campaign into `pr-hall-of-helix`, under a `pr-` -prefix for public-relations campaigns. After that split, publishing the Hall of -Helix is an *example* of using fluid-core and fluid-telegram rather than the -thing they are for — which is the state this framework needs to reach before -`fluid-x` and `fluid-substack` are worth starting. - -Do it after the loop has closed at least once. Extracting a seam before you have -used it is how you find out you cut it in the wrong place. +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-0009-campaign-repos-and-example-separation.md b/workplans/FLUID-WP-0009-campaign-repos-and-example-separation.md deleted file mode 100644 index 0f5dc3a..0000000 --- a/workplans/FLUID-WP-0009-campaign-repos-and-example-separation.md +++ /dev/null @@ -1,132 +0,0 @@ ---- -id: FLUID-WP-0009 -type: workplan -title: "Establish the pr- campaign repository pattern and reduce hall-of-helix to an example" -domain: infotech -repo: fluid-core -status: active -owner: worsch -topic_slug: fluid-core -created: "2026-09-04" -updated: "2026-09-04" -planning_priority: medium -planning_order: 9 -depends_on: - - FLUID-WP-0008 -related_repos: - - fluid-telegram - - fluid-x - - fluid-substack - - hall-of-helix - - helix-forge -state_hub_workstream_id: "b6459dd0-fc4f-54a4-a2e9-d7b83cff6c6e" ---- - -# FLUID-WP-0009 — Campaign repositories and example separation - -Two things this framework needs before it is genuinely reusable, and neither is -urgent enough to block the first Telegram publication. - -**A place for campaigns to live.** Publishing the Hall of Helix is an ongoing -public-relations offering with its own editorial voice, cadence, consent -records and budget. Today it lives inside `FT-WP-0001`, a workplan that is -supposed to finish. It needs a repository. - -**A framework that is not about one campaign.** While the only worked example is -the Hall of Helix, it is hard to tell which parts of fluid-core and -fluid-telegram are general and which quietly assume that particular use. The -test of a framework is a second consumer, and the honest way to find the -accidental couplings is to look for them deliberately. - -This workplan should start after `FT-WP-0001` T12 — the loop closing once on -real traffic. Extracting a seam before using it is how you learn you cut it in -the wrong place. - -## T01 — Define the pr- repository convention - -```task -id: FLUID-WP-0009-T01 -status: todo -priority: medium -state_hub_task_id: "2a3aab85-e464-5b17-980f-1896ecd3794f" -``` - -Write down what a `pr-` repository is, so the second one does not have to be -reverse-engineered from the first. - -The prefix covers public relations, promotion and propaganda — deliberately -including the uncomfortable third word, because a campaign repository that -cannot name what it is doing will not govern it well. - -A `pr-` repo should carry: its own `INTENT.md`; a campaign intent describing -audience, voice and what it will not say; consent records for anyone it writes -about; composition strategies as named variants; and a publishing cadence. It -consumes one or more `fluid-*` interfaces and owns none of them. - -## T02 — Establish pr-hall-of-helix - -```task -id: FLUID-WP-0009-T02 -status: todo -priority: medium -state_hub_task_id: "7d40da89-9a4d-5358-ae2b-7a1a1339efff" -``` - -Create the repository and move the campaign into it, per `FT-WP-0001` T13. The -split is already written there; this task is the execution and the registration -with the State Hub under the `infotech` domain. - -The campaign becomes a consumer of `fluid-telegram` in cohort -`hall-publishing-jobs`, and runs its own FLUID loop over its own artifacts — -which composition variant travels — using the per-variant engagement the -interface reports. - -## T03 — Find what fluid-core assumes about the Hall of Helix - -```task -id: FLUID-WP-0009-T03 -status: todo -priority: medium -state_hub_task_id: "d9c69244-19ef-5eaf-9f43-d0374d6278de" -``` - -Audit fluid-core for anything that assumes this particular campaign. The -conformance handover test already couples the two repositories deliberately; -check whether anything else does so by accident. - -Known candidates to check rather than assume clean: default redaction patterns, -the classifier's thresholds, the fitness metric names, and whether -`examples/echo-interface` is doing enough work to be the general example it -claims to be. - -## T04 — Reduce the Telegram case to a documented example - -```task -id: FLUID-WP-0009-T04 -status: todo -priority: low -state_hub_task_id: "ec77e62c-1251-5331-a1a8-42985fb97b94" -``` - -Once the campaign lives elsewhere, `docs/handover/fluid-telegram.md` becomes a -worked example rather than a live handover: here is a real interface, here is -the pressure it faced, here is how the loop closed on it. - -That is more valuable as documentation than it is as history, and it is the -form `fluid-x` and `fluid-substack` will want to read. - -## T05 — Prove reuse with a second interface - -```task -id: FLUID-WP-0009-T05 -status: todo -priority: low -state_hub_task_id: "227d0d53-f325-5d95-8a18-d56603c820fd" -``` - -The real test of T03. Take `fluid-x` or `fluid-substack` from empty capability -canon to a published R-1, and record honestly how much of the fluid-telegram -handover transferred unchanged. - -Whatever did not transfer is the accidental coupling, and finding it this way is -cheaper than arguing about it in advance.