diff --git a/conformance/handover_test.go b/conformance/handover_test.go index 74240d7..94e61d1 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/hall-entries": false, - "POST /v1/hall-entries": false, - "GET /v1/hall-entries/{entry_id}": false, + "GET /v1/channel-posts": false, + "POST /v1/channel-posts": false, + "GET /v1/channel-posts/{post_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 8f0c2bd..cddf28d 100644 --- a/docs/handover/fluid-telegram.md +++ b/docs/handover/fluid-telegram.md @@ -5,7 +5,13 @@ **Date:** 2026-09-04 The first real FLUID interface: a Telegram identity for HelixForge, and a -channel publishing hall-of-helix entries. +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. --- @@ -22,8 +28,8 @@ 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: +A hall entry and a channel post are written for different readers. Measured +against `hall-of-helix` on 2026-09-04: | | | |---|---| @@ -33,15 +39,38 @@ Telegram limits a single message to 4096 characters. Measured against | 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. +| 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. 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 @@ -53,9 +82,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-length-limit.yaml` | -| Competing hypotheses | `fluid-telegram/hypotheses/H-tg-{teaser,serial}.yaml` | -| First experiment | `fluid-telegram/experiments/E-tg-length.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` | | Adapter connector contract | `fluid-telegram/docs/adapter-contract.md` | | Cohorts, telemetry, redaction | `fluid-telegram/docs/observation.md` | | Runbook | `fluid-telegram/docs/runbook.md` | @@ -70,22 +99,26 @@ 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. +This interface's first experiment is about **delivery**, which is what it owns. -**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-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-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.* +**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.* -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. +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. -Neither is obviously right. That is the point. +**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. --- @@ -101,12 +134,11 @@ 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. +**`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. **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 @@ -135,20 +167,25 @@ Four things need a person; nothing in the framework can or should do them. ## 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`. +`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`. -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. +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. -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. +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. --- @@ -168,6 +205,17 @@ 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 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. +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. diff --git a/workplans/FLUID-WP-0009-campaign-repos-and-example-separation.md b/workplans/FLUID-WP-0009-campaign-repos-and-example-separation.md new file mode 100644 index 0000000..00e7477 --- /dev/null +++ b/workplans/FLUID-WP-0009-campaign-repos-and-example-separation.md @@ -0,0 +1,126 @@ +--- +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 +--- + +# 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 +``` + +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 +``` + +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 +``` + +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 +``` + +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 +``` + +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.