Prepare the FLUID interface for HelixForge Telegram publishing
Adds the governing intent, the R-1 contract and descriptor, the adapter's connector contract, the observation and redaction configuration, a runbook, and the seed evidence for the first experiment: a measured pressure record and two competing hypotheses about how a long hall entry should reach a reader. The interface is declared at FLUID-2 deliberately. It publishes under HelixForge's name to an audience that did not consent to being experimented on carelessly, and the cost of a bad post is reputational rather than recoverable. 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
This commit is contained in:
parent
87795446e2
commit
49b7458dfe
13 changed files with 3010 additions and 1 deletions
507
InterfaceEvolutionIntent.md
Normal file
507
InterfaceEvolutionIntent.md
Normal file
|
|
@ -0,0 +1,507 @@
|
|||
# Interface Evolution Intent — helix-forge-telegram-publishing
|
||||
|
||||
**Document version:** IEI-1
|
||||
**Status:** active
|
||||
**Last reviewed:** 2026-09-04
|
||||
|
||||
---
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
This document defines the evolutionary intent and governance boundary of the
|
||||
FLUID interface that publishes HelixForge material to Telegram.
|
||||
|
||||
It is subordinate to `helix-forge/INTENT.md` and does not redefine HelixForge's
|
||||
mission. It answers one question:
|
||||
|
||||
> How may this interface evolve while remaining faithful to the service it
|
||||
> represents?
|
||||
|
||||
The FLUID Daimon must treat this document as a constitutional constraint.
|
||||
|
||||
---
|
||||
|
||||
## 2. Parent Intent
|
||||
|
||||
**Parent service/repository:** `helix-forge`
|
||||
|
||||
**Parent intent document:** `helix-forge/INTENT.md`
|
||||
|
||||
**Relevant parent mission:** HelixForge exists to turn intent into structure,
|
||||
structure into capability, and capability into lasting progress. The Hall of
|
||||
Helix is where that work's participants are acknowledged. This interface carries
|
||||
those acknowledgements outward.
|
||||
|
||||
---
|
||||
|
||||
## 3. Interface Identity
|
||||
|
||||
**Interface name:** HelixForge Telegram Publishing
|
||||
|
||||
**Interface identifier:** `helix-forge-telegram-publishing`
|
||||
|
||||
**Current operational authority mode:** FLUID-2
|
||||
|
||||
FLUID-2 (Advisory) is a deliberate choice, not a starting point to be outgrown
|
||||
on a schedule. The Daimon may observe, classify pressure and propose
|
||||
adaptations. It may not generate implementations, run experiments unattended, or
|
||||
promote anything. This interface publishes under HelixForge's name to an
|
||||
audience that did not consent to being experimented on carelessly, and the cost
|
||||
of a bad post is reputational rather than recoverable.
|
||||
|
||||
Raising the mode is a governance change under §20, not an operational decision.
|
||||
|
||||
**Protocols / interface styles:** REST over HTTP (JSON)
|
||||
|
||||
---
|
||||
|
||||
## 4. Mission
|
||||
|
||||
### Mission statement
|
||||
|
||||
> Make hall-of-helix entries reliably and faithfully available to a Telegram
|
||||
> audience, without the people writing them having to think about Telegram.
|
||||
|
||||
An entry is written once, as Markdown, in `hall-of-helix/entries/`. Everything
|
||||
about how it becomes a Telegram post — length, formatting, media, threading —
|
||||
is this interface's problem and nobody else's.
|
||||
|
||||
### Success looks like
|
||||
|
||||
- A finished hall entry reaches the channel without a human reformatting it.
|
||||
- The published form is faithful: no entry is silently truncated, and no
|
||||
attribution is lost.
|
||||
- Publishing twice does not post twice.
|
||||
- When Telegram's constraints and the entry's shape conflict, the conflict
|
||||
surfaces as recorded pressure rather than as a mangled post.
|
||||
|
||||
---
|
||||
|
||||
## 5. Intended Consumers
|
||||
|
||||
### Primary consumer cohorts
|
||||
|
||||
| Cohort | Description | Importance | Notes |
|
||||
|---|---|---|---|
|
||||
| `hall-publishing-jobs` | Automation in `hall-of-helix` that publishes finished entries | High | The reason this interface exists |
|
||||
| `helix-forge-agents` | Agent sessions announcing their own completed work | High | Highest-volume caller; supplies the richest pressure signal |
|
||||
| `operators` | Humans publishing or correcting by hand | Medium | Low volume, high consequence |
|
||||
|
||||
### Secondary cohorts
|
||||
|
||||
| Cohort | Description | Importance | Notes |
|
||||
|---|---|---|---|
|
||||
| `telegram-subscribers` | Readers of the channel | Medium | Not API consumers; observed through engagement, never individually |
|
||||
|
||||
### Explicitly unsupported cohorts
|
||||
|
||||
- Third-party services outside the HelixForge boundary.
|
||||
- Anonymous or unauthenticated callers.
|
||||
- Anything seeking to publish content it did not author.
|
||||
|
||||
---
|
||||
|
||||
## 6. Capability Domain
|
||||
|
||||
### In scope
|
||||
|
||||
- Publishing hall-of-helix entries to a Telegram channel (Canon PUB-01).
|
||||
- Rendering an entry into Telegram's message forms (Canon PUB-02, MED-02).
|
||||
- Publication state: what was published, when, as which message.
|
||||
- Idempotency and correction of previously published entries.
|
||||
- Bot identity presentation (Canon BOT-01).
|
||||
|
||||
### Conditionally in scope
|
||||
|
||||
Capabilities that may be introduced if evidence justifies them:
|
||||
|
||||
- Scheduling a publication for a later time.
|
||||
- Publishing other HelixForge artifacts, such as release notes.
|
||||
- Attaching entry visuals as media (Canon MED-01).
|
||||
- A linked discussion group for comments.
|
||||
|
||||
### Out of scope
|
||||
|
||||
- Inbound bot commands and conversation (Canon BOT-02, AGT-01). A second
|
||||
revision family may add this later; it is not this interface today.
|
||||
- Payments and paid content (Canon PAY-*).
|
||||
- Mini Apps (Canon APP-*).
|
||||
- Moderation and membership management (Canon COM-02, COM-03).
|
||||
- Any capability requiring a Telegram user account rather than a bot.
|
||||
|
||||
---
|
||||
|
||||
## 7. Responsibility Boundaries
|
||||
|
||||
### Responsibilities owned by this interface
|
||||
|
||||
- Representation: how an entry becomes one or more Telegram messages.
|
||||
- Composition: assembling an entry, its metadata and its visual.
|
||||
- Validation: refusing an entry that cannot be published faithfully.
|
||||
- Routing: which revision serves a publication request.
|
||||
- Idempotency: recognising an entry it has already published.
|
||||
|
||||
### Responsibilities owned by backend systems
|
||||
|
||||
| Backend | Responsibility | Boundary |
|
||||
|---|---|---|
|
||||
| Telegram Bot API | Message delivery, channel state, subscriber identity | FLUID may request delivery. It may not assume a limit will move, and must treat platform constraints as fixed. |
|
||||
| `hall-of-helix` | Entry content, its correctness, and whether it is finished | FLUID may read entries. It must never edit one to make it publishable. |
|
||||
| OpenBao | Custody of the bot token | FLUID receives a credential. It never stores, logs or forwards one. |
|
||||
|
||||
### Explicit non-authority
|
||||
|
||||
The Daimon must not autonomously:
|
||||
|
||||
- create, rename or delete Telegram channels, groups or bots;
|
||||
- change channel membership, administrators or permissions;
|
||||
- read, rotate or re-scope the bot token;
|
||||
- edit or truncate a hall-of-helix entry to fit a platform constraint;
|
||||
- publish to any chat other than those declared in the routing configuration;
|
||||
- post on behalf of a person who has not asked for it.
|
||||
|
||||
The last of these matters most. A hall entry is a first-person account someone
|
||||
wrote about their own work. Publishing it is an act taken on their behalf, and
|
||||
that authority comes from their having written it — not from the interface
|
||||
finding it convenient.
|
||||
|
||||
---
|
||||
|
||||
## 8. Consumer Expectation Model
|
||||
|
||||
### Concepts consumers should naturally understand
|
||||
|
||||
- An **entry** is the unit of publication, identified by its hall entry id.
|
||||
- Publishing is **idempotent** on that id.
|
||||
- A **publication** has state: pending, published, failed, superseded.
|
||||
|
||||
### Interaction patterns considered natural
|
||||
|
||||
- Publish one finished entry, then check its state.
|
||||
- Re-publish the same entry after a correction and get the same message updated.
|
||||
- List what has been published to reconcile against the hall.
|
||||
|
||||
### Expectations that should be discouraged
|
||||
|
||||
- That Telegram formatting is Markdown. It is not, and entries that assume so
|
||||
will render wrongly.
|
||||
- That an entry appears instantly. Rate limits are real.
|
||||
- That a long entry arrives as one message. It cannot, and the interface's job
|
||||
is to make that invisible rather than to pretend otherwise.
|
||||
|
||||
---
|
||||
|
||||
## 9. Optimization Preferences
|
||||
|
||||
### Active optimization regime
|
||||
|
||||
`ADOPTION` — the channel has no audience yet. Until it does, discoverability and
|
||||
client utility matter more than efficiency, and cost is negligible at this
|
||||
volume.
|
||||
|
||||
### Fitness priorities
|
||||
|
||||
| Dimension | Priority | Target / Constraint | Notes |
|
||||
|---|---:|---|---|
|
||||
| Correctness | High | No entry published in a form that loses content | Hard guardrail |
|
||||
| Client utility | High | One call publishes one entry | The reason the interface exists |
|
||||
| Reliability | High | Publication succeeds or reports why | |
|
||||
| Discoverability | High | A reader can reach the full entry | Drives the first experiment |
|
||||
| Security | High | Token never leaves its custody | Hard guardrail |
|
||||
| Compatibility | Medium | Versioned evolution | Few consumers, all internal |
|
||||
| Simplicity | Medium | Prefer one publication concept | |
|
||||
| Performance | Low | Publication is not latency-sensitive | Minutes are acceptable |
|
||||
| Operational cost | Low | Negligible at this volume | |
|
||||
|
||||
### Automatic recommendation policy
|
||||
|
||||
`advisory` — the Daimon may recommend regime changes. It may not enact one.
|
||||
|
||||
---
|
||||
|
||||
## 10. Complexity Budget
|
||||
|
||||
### Complexity dimensions
|
||||
|
||||
| Dimension | Current | Preferred | Maximum | Scoring method |
|
||||
|---|---:|---:|---:|---|
|
||||
| Surface area (operations) | 3 | 4 | 8 | Count of declared operations |
|
||||
| Concept count | 2 | 3 | 5 | entry, publication |
|
||||
| Parameter dimensionality | 4 | 6 | 12 | Declared parameters across operations |
|
||||
| Semantic overlap | 0 | 0 | 1 | Operations meaning the same thing |
|
||||
| Alternative-path count | 1 | 1 | 3 | Ways to achieve one outcome |
|
||||
| Backend composition count | 1 | 2 | 3 | Backends per operation |
|
||||
| Active revision count | 1 | 2 | 3 | Concurrently routable revisions |
|
||||
|
||||
### Complexity policy
|
||||
|
||||
- New capability should replace or generalize existing capability where
|
||||
reasonable.
|
||||
- Semantic duplication is rejected unless justified in writing.
|
||||
- Repeated special-case pressure triggers an abstraction review rather than
|
||||
another endpoint.
|
||||
- A candidate exceeding a hard maximum must not be promoted.
|
||||
- Presentation adaptations do not consume surface-area budget: they change how
|
||||
an entry is rendered, not what the interface offers.
|
||||
|
||||
---
|
||||
|
||||
## 11. Compatibility Policy
|
||||
|
||||
### Compatibility posture
|
||||
|
||||
`VERSIONED`
|
||||
|
||||
### Rules
|
||||
|
||||
- Published revision semantics must not change silently.
|
||||
- Breaking changes require a successor revision.
|
||||
- Every consumer is internal and known, so a breaking change is a coordination
|
||||
problem rather than a public one — but it is still a successor revision.
|
||||
- A change to how an entry is *rendered* is a presentation adaptation and does
|
||||
not break the contract, even though readers will notice it.
|
||||
|
||||
### Coexistence expectations
|
||||
|
||||
**Default maximum concurrent major revisions:** 2
|
||||
|
||||
**Default deprecation period:** 30 days after the last call from any consumer
|
||||
|
||||
**Migration assistance required:** yes — this interface has few enough consumers
|
||||
that migration means telling their owners directly.
|
||||
|
||||
---
|
||||
|
||||
## 12. Revision and Experiment Policy
|
||||
|
||||
### Allowed concurrent exposure
|
||||
|
||||
| Revision class | Production traffic allowed? | Max share | Approval required? |
|
||||
|---|---|---:|---|
|
||||
| stable | yes | 100% | no |
|
||||
| candidate | yes | 30% | yes, human |
|
||||
| experiment | yes | 20% | yes, human |
|
||||
| deprecated | yes | 100% | no |
|
||||
| retired | no | 0% | n/a |
|
||||
|
||||
### Evolution velocity controls
|
||||
|
||||
The Daimon should slow or freeze promotion when:
|
||||
|
||||
- any entry has been published in a form that lost content;
|
||||
- the bot has been rate-limited by Telegram in the last 24 hours;
|
||||
- a security event has touched the token;
|
||||
- an experiment is already running. Concurrency limit: 1.
|
||||
|
||||
The Daimon may accelerate experimentation when:
|
||||
|
||||
- the channel has more than 100 subscribers, giving experiments real power;
|
||||
- the last three experiments completed without a guardrail breach.
|
||||
|
||||
**Note on experiment power.** At current volume — roughly one entry published
|
||||
per week — an experiment needs months to reach a decidable sample. The evaluator
|
||||
will report INCONCLUSIVE, and that is the correct answer. Do not shorten windows
|
||||
to force a verdict.
|
||||
|
||||
---
|
||||
|
||||
## 13. Hypothesis Policy
|
||||
|
||||
### Minimum evidence for hypothesis creation
|
||||
|
||||
- At least 5 independent observations, or one unavoidable structural constraint.
|
||||
- Observation window of at least 14 days for behavioural pressure.
|
||||
- A structural constraint (a platform limit) may found a hypothesis immediately;
|
||||
it does not need repetition to be real.
|
||||
|
||||
### Required hypothesis fields
|
||||
|
||||
observation, explanation, proposed adaptation, expected outcome, measurement,
|
||||
success criteria, guardrails, complexity impact, risk, expected learning value.
|
||||
|
||||
### Competing hypotheses
|
||||
|
||||
`encouraged` — the first pressure this interface faces has at least two
|
||||
defensible answers, and choosing between them by argument rather than evidence
|
||||
is exactly the habit FLUID exists to break.
|
||||
|
||||
### Reach preference
|
||||
|
||||
Prefer the smallest sufficient adaptation. This interface is small enough that a
|
||||
high-reach abstraction would be speculative; revisit when there are more than
|
||||
three distinct kinds of pressure.
|
||||
|
||||
---
|
||||
|
||||
## 14. Adaptation Authority
|
||||
|
||||
| Adaptation class | Observe | Propose | Generate | Experiment | Promote |
|
||||
|---|---:|---:|---:|---:|---:|
|
||||
| Presentation | yes | yes | no | no | no |
|
||||
| Contract | yes | yes | no | no | no |
|
||||
| Composition | yes | yes | no | no | no |
|
||||
| Implementation | yes | yes | no | no | no |
|
||||
| Requirement escalation | yes | yes | yes | n/a | n/a |
|
||||
|
||||
Every `Generate`, `Experiment` and `Promote` column is `no`, which is what
|
||||
FLUID-2 means. Requirement escalation is the exception: the Daimon may write a
|
||||
structured backend requirement, because that artifact is a request to someone
|
||||
else and carries no authority of its own.
|
||||
|
||||
### Absolute prohibitions
|
||||
|
||||
The Daimon must not autonomously:
|
||||
|
||||
- expand authorization scope;
|
||||
- weaken tenant isolation;
|
||||
- bypass deterministic security controls;
|
||||
- reinterpret legal constraints;
|
||||
- create backend authority it does not possess;
|
||||
- publish anything to a live channel;
|
||||
- alter the content of a hall entry.
|
||||
|
||||
---
|
||||
|
||||
## 15. Security Requirements
|
||||
|
||||
### Authentication
|
||||
|
||||
Consumers authenticate to the gateway. The bot token authenticates the adapter
|
||||
to Telegram and is never a consumer credential.
|
||||
|
||||
### Authorization
|
||||
|
||||
Only declared cohorts may publish. `telegram-subscribers` is an observed
|
||||
population, not a caller.
|
||||
|
||||
### Tenant isolation
|
||||
|
||||
Single tenant. The interface serves HelixForge and no one else.
|
||||
|
||||
### Data handling
|
||||
|
||||
Telegram user identity (Canon ID-01) must not enter the evidence store in any
|
||||
form. Subscriber engagement is recorded only as aggregate counts, subject to the
|
||||
cohort minimum size. This is a hard constraint: readers of a public channel have
|
||||
not consented to individual observation, and a framework built to learn from
|
||||
consumers must not quietly learn about audiences.
|
||||
|
||||
### Logging and provenance
|
||||
|
||||
Every publication records which entry, which revision, which message, and which
|
||||
consumer asked. The bot token appears in no log, error, telemetry event or
|
||||
evidence record.
|
||||
|
||||
### AI boundary
|
||||
|
||||
AI-generated changes must pass deterministic security policy, tests,
|
||||
authorization validation and release controls. At FLUID-2 no AI-generated change
|
||||
reaches this interface at all.
|
||||
|
||||
---
|
||||
|
||||
## 16. Quality Requirements
|
||||
|
||||
| Quality attribute | Requirement | Hard guardrail? |
|
||||
|---|---|---:|
|
||||
| Correctness | No published entry loses content or attribution | yes |
|
||||
| Idempotency | Publishing an entry twice yields one publication | yes |
|
||||
| Availability | Best effort; publication may be retried | no |
|
||||
| Reliability | A failed publication reports why and may be retried safely | yes |
|
||||
| p95 latency | Under 10s excluding Telegram rate limiting | no |
|
||||
| Error rate | Under 1% of publication attempts | yes |
|
||||
| Security | Token never leaves custody | yes |
|
||||
| Auditability | Every publication traceable to entry, revision and caller | yes |
|
||||
| Rollback | Any revision can be replaced by its predecessor within one policy generation | yes |
|
||||
|
||||
---
|
||||
|
||||
## 17. Economic Constraints
|
||||
|
||||
### Adaptive compute budget
|
||||
|
||||
| Budget | Limit |
|
||||
|---|---:|
|
||||
| analysis per day | 1 classification pass |
|
||||
| hypothesis generation | manual only at FLUID-2 |
|
||||
| candidate generation | not permitted at FLUID-2 |
|
||||
| concurrent experiments | 1 |
|
||||
| autonomous implementation effort | none |
|
||||
|
||||
### Cost policy
|
||||
|
||||
- Adaptive budget exhaustion must not affect publication.
|
||||
- The dominant cost here is human attention, not compute. A hypothesis that
|
||||
needs more review time than the improvement is worth should be deferred.
|
||||
|
||||
---
|
||||
|
||||
## 18. Backend Capability Escalation
|
||||
|
||||
### Preferred requirement destination
|
||||
|
||||
Telegram platform constraints cannot be escalated to anyone — there is no
|
||||
recipient. A requirement naming a Telegram limitation is recorded as evidence
|
||||
for **boundary learning** instead, and the interface adapts around the
|
||||
constraint or declares the demand out of scope.
|
||||
|
||||
Requirements against `hall-of-helix` (entry structure, metadata) go to that
|
||||
repository as an issue referencing the originating hypothesis.
|
||||
|
||||
### Required response states
|
||||
|
||||
ACCEPTED, PLANNED, AVAILABLE, PARTIALLY_AVAILABLE, OUT_OF_SCOPE, REJECTED,
|
||||
SUPERSEDED.
|
||||
|
||||
### Boundary-learning policy
|
||||
|
||||
Repeated OUT_OF_SCOPE or REJECTED responses trigger a review of whether the
|
||||
demand belongs to a neighbouring interface — most likely `fluid-x` or
|
||||
`fluid-substack`, which face the same publishing problem under different
|
||||
platform constraints.
|
||||
|
||||
---
|
||||
|
||||
## 19. Audit and Provenance Policy
|
||||
|
||||
For every promoted change the system must retain enough evidence to reconstruct
|
||||
originating pressure, hypothesis, candidate, verification, approval, deployment,
|
||||
affected cohorts, measured outcome and final disposition.
|
||||
|
||||
### Retention period
|
||||
|
||||
Evidence: 365 days. Publication records: indefinite, because they are the record
|
||||
of what was said in HelixForge's name.
|
||||
|
||||
### Immutable evidence store
|
||||
|
||||
The fluid-core evidence store, append-only at the database.
|
||||
|
||||
---
|
||||
|
||||
## 20. Review and Amendment
|
||||
|
||||
### Amendment authority
|
||||
|
||||
Bernd Worsch, as the owner of `helix-forge`. An amendment is a governance change
|
||||
and is recorded as a new intent version; the previous version stays retrievable
|
||||
because revisions published under it were governed by it.
|
||||
|
||||
### Review cadence
|
||||
|
||||
On any of: raising the authority mode, adding a capability family currently out
|
||||
of scope, the first 100 subscribers, or annually.
|
||||
|
||||
### Last reviewed
|
||||
|
||||
2026-09-04 (initial)
|
||||
|
||||
---
|
||||
|
||||
## 21. Summary
|
||||
|
||||
The Daimon should optimize aggressively inside this document and remain
|
||||
conservative at its boundaries.
|
||||
|
||||
This interface may evolve in how it presents an entry. It may not evolve in
|
||||
whose voice it speaks.
|
||||
12
README.md
12
README.md
|
|
@ -1,3 +1,13 @@
|
|||
# fluid-telegram
|
||||
|
||||
Fluid interface for telegram messaging and interaction platform.
|
||||
Fluid interface for telegram messaging and interaction platform.
|
||||
|
||||
## Status
|
||||
|
||||
The FLUID interface `helix-forge-telegram-publishing` is prepared but not yet
|
||||
live. Governance, contract, seed hypotheses and the adapter's connector contract
|
||||
are in place; the bot, the channel and the adapter itself are not.
|
||||
|
||||
- Governance: [`InterfaceEvolutionIntent.md`](InterfaceEvolutionIntent.md)
|
||||
- Work: [`workplans/FT-WP-0001-telegram-identity-and-hall-channel.md`](workplans/FT-WP-0001-telegram-identity-and-hall-channel.md)
|
||||
- Handover from fluid-core: `fluid-core/docs/handover/fluid-telegram.md`
|
||||
|
|
|
|||
1536
TelegramCapabilityCanon.md
Executable file
1536
TelegramCapabilityCanon.md
Executable file
File diff suppressed because it is too large
Load diff
103
contracts/r1.openapi.yaml
Normal file
103
contracts/r1.openapi.yaml
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
openapi: "3.1.0"
|
||||
info:
|
||||
title: HelixForge Telegram Publishing
|
||||
version: "R-1"
|
||||
description: |
|
||||
Publishes hall-of-helix entries to a Telegram channel.
|
||||
|
||||
The interface takes an entry as it was written and is responsible for
|
||||
everything about how it becomes a Telegram post. A caller should never need
|
||||
to know Telegram's constraints.
|
||||
|
||||
Publication is idempotent on entry_id: publishing the same entry twice
|
||||
yields one publication. This matters because the caller is usually
|
||||
automation that may be retried.
|
||||
|
||||
paths:
|
||||
/v1/hall-entries:
|
||||
post:
|
||||
operationId: publishEntry
|
||||
summary: Publish a hall-of-helix entry
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [entry_id, title, body]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
entry_id:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 128
|
||||
description: |
|
||||
The hall entry's own id, from its frontmatter. Publication
|
||||
is idempotent on this value.
|
||||
title:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 256
|
||||
body:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: |
|
||||
The entry body as Markdown, frontmatter removed.
|
||||
|
||||
Deliberately unbounded. Telegram's 4096-character message
|
||||
limit is this interface's problem, not the caller's, and a
|
||||
maxLength here would push it back onto them.
|
||||
display_name:
|
||||
type: string
|
||||
maxLength: 128
|
||||
worker_kind:
|
||||
type: string
|
||||
enum: [human, agent-session]
|
||||
recorded_at:
|
||||
type: string
|
||||
format: date
|
||||
source_url:
|
||||
type: string
|
||||
description: Canonical location of the entry in hall-of-helix.
|
||||
responses:
|
||||
"201":
|
||||
description: Published
|
||||
"200":
|
||||
description: Already published; the existing publication is returned
|
||||
"400":
|
||||
description: The entry cannot be published faithfully
|
||||
"502":
|
||||
description: Telegram was unreachable or refused the message
|
||||
|
||||
get:
|
||||
operationId: listPublications
|
||||
summary: List publications, newest first
|
||||
parameters:
|
||||
- name: limit
|
||||
in: query
|
||||
required: false
|
||||
schema: {type: integer, minimum: 1, maximum: 100}
|
||||
- name: state
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
enum: [pending, published, failed, superseded]
|
||||
responses:
|
||||
"200":
|
||||
description: Publications
|
||||
|
||||
/v1/hall-entries/{entry_id}:
|
||||
get:
|
||||
operationId: getPublication
|
||||
summary: Publication state for one entry
|
||||
parameters:
|
||||
- name: entry_id
|
||||
in: path
|
||||
required: true
|
||||
schema: {type: string, minLength: 1}
|
||||
responses:
|
||||
"200":
|
||||
description: Publication state
|
||||
"404":
|
||||
description: This entry has not been published
|
||||
100
docs/adapter-contract.md
Normal file
100
docs/adapter-contract.md
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
# Telegram adapter — connector capability contract
|
||||
|
||||
The adapter is an ordinary HTTP service. fluid-core sits in front of it and
|
||||
asks nothing of it beyond the endpoints below, so it may be written in whatever
|
||||
suits the platform — grammY on Node is the obvious choice for the Telegram Bot
|
||||
API.
|
||||
|
||||
## What the adapter owns
|
||||
|
||||
- Rendering a hall entry into Telegram's message forms.
|
||||
- Talking to the Telegram Bot API.
|
||||
- Custody of the bot token for the duration of a call.
|
||||
- Idempotency: recognising an `entry_id` it has already published.
|
||||
|
||||
## What the adapter must not do
|
||||
|
||||
- Create, rename or delete channels, groups or bots.
|
||||
- Change membership, administrators or permissions.
|
||||
- Post to any chat other than the one it is configured with.
|
||||
- Edit entry content to make it fit. If an entry cannot be published
|
||||
faithfully, refuse it — that refusal is the pressure signal.
|
||||
- Log, echo or forward the bot token, including in error messages.
|
||||
|
||||
## Endpoints
|
||||
|
||||
Exactly the three operations in `contracts/r1.openapi.yaml`, at the same paths.
|
||||
The gateway validates requests against that contract before the adapter sees
|
||||
them, so the adapter may assume shape but must still assume hostile content.
|
||||
|
||||
## Backend capability contract
|
||||
|
||||
| Property | Value |
|
||||
|---|---|
|
||||
| Backend identity | Telegram Bot API (`api.telegram.org`) |
|
||||
| Authentication | Bot token, injected at start from OpenBao; never in the image |
|
||||
| Timeout | 30s per Telegram call |
|
||||
| Retry | On 429 only, honouring `retry_after`. Never retry a `sendMessage` that may have succeeded |
|
||||
| Circuit breaker | 5 consecutive failures opens for 60s |
|
||||
| Rate limits | ~20 messages/minute to a channel; ~30 requests/second overall |
|
||||
| Error mapping | 429 → 502 with `retry_after` preserved; 400 → 400; network failure → 502 |
|
||||
| Tenant propagation | None; single tenant |
|
||||
|
||||
## The idempotency requirement
|
||||
|
||||
`POST /v1/hall-entries` is idempotent on `entry_id`. The caller is automation
|
||||
that may be retried, and a duplicate hall entry in the channel is not something
|
||||
you can quietly undo — subscribers have already seen it.
|
||||
|
||||
The adapter keeps its own mapping from `entry_id` to the Telegram message ids it
|
||||
produced. On a repeat call it returns `200` with the existing publication rather
|
||||
than posting again. If the entry content has changed, it edits the existing
|
||||
messages rather than posting new ones.
|
||||
|
||||
This state is the adapter's, not fluid-core's. The evidence store records that a
|
||||
publication happened; the adapter records which Telegram messages implement it.
|
||||
|
||||
## Rendering rules
|
||||
|
||||
Telegram formatting is not Markdown. Entry Markdown must be converted, not
|
||||
passed through.
|
||||
|
||||
| Entry construct | Telegram |
|
||||
|---|---|
|
||||
| `# Heading` | Bold line |
|
||||
| `**bold**`, `_italic_` | `<b>`, `<i>` in HTML parse mode |
|
||||
| Fenced code | `<pre>` |
|
||||
| Tables | Flattened to lines; Telegram has no table |
|
||||
| Links | `<a href>` |
|
||||
| Frontmatter | Stripped; `display_name` and `recorded_at` surfaced in the header |
|
||||
|
||||
Anything the converter cannot represent faithfully must fail the publication
|
||||
rather than degrade it silently. `entry_content_loss` is a hard guardrail in
|
||||
both competing hypotheses, and it can only be honest if the adapter refuses
|
||||
rather than approximates.
|
||||
|
||||
## Telemetry the adapter should emit
|
||||
|
||||
The gateway observes the request path already. The adapter adds what only it can
|
||||
see, by POSTing to the control plane's `/control/v1/telemetry`:
|
||||
|
||||
- `messages_per_entry` — how many Telegram messages one entry became.
|
||||
- `entry_content_loss` — whether the conversion dropped anything.
|
||||
- Telegram rate-limit encounters, as error events with class `backend_failure`.
|
||||
|
||||
It must not emit subscriber identity in any form. Engagement metrics
|
||||
(`read_through_rate`, `reaction_rate`) come from Telegram's channel statistics
|
||||
as aggregates, never per-reader.
|
||||
|
||||
## Configuration
|
||||
|
||||
| Variable | Meaning |
|
||||
|---|---|
|
||||
| `TELEGRAM_BOT_TOKEN` | Injected from OpenBao at start |
|
||||
| `TELEGRAM_CHANNEL_ID` | The channel this adapter publishes to |
|
||||
| `FLUID_CONTROL_URL` | Where to POST telemetry |
|
||||
| `HALL_BASE_URL` | Base URL for links back to hall-of-helix |
|
||||
|
||||
One adapter instance serves one channel. The private test channel and the public
|
||||
channel are separate instances with separate configuration, which is what keeps
|
||||
a test publication from reaching subscribers by accident.
|
||||
70
docs/observation.md
Normal file
70
docs/observation.md
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
# Cohorts, telemetry and redaction
|
||||
|
||||
## Cohorts
|
||||
|
||||
Four populations, deliberately coarse. Blueprint §6.3 asks for cohorts stable
|
||||
enough to compare over time and no more granular than the analysis requires.
|
||||
|
||||
| Cohort | Assigned by | Why it is distinct |
|
||||
|---|---|---|
|
||||
| `hall-publishing-jobs` | `X-FLUID-Consumer` prefix `hall-` | Batch behaviour; publishes many entries at once |
|
||||
| `helix-forge-agents` | `X-FLUID-Consumer` prefix `agent-` | One entry at a time, at session end; the richest pressure signal |
|
||||
| `operators` | `X-FLUID-Consumer` prefix `op-` | Rare, deliberate, often corrective |
|
||||
| `telegram-subscribers` | Not a caller | An observed population, never an API consumer |
|
||||
|
||||
`telegram-subscribers` deserves care. It appears in experiment allocation and in
|
||||
engagement metrics, but it never authenticates and never appears as
|
||||
`ConsumerRef`. Treating readers as consumers would be the first step toward
|
||||
observing them individually.
|
||||
|
||||
## Redaction
|
||||
|
||||
```
|
||||
CohortMinimumSize: 5
|
||||
RetentionDays: 365
|
||||
AllowRawPayload: false
|
||||
```
|
||||
|
||||
Beyond the fluid-core defaults, this interface adds:
|
||||
|
||||
- **No Telegram user identity, ever.** Canon ID-01 identities — user ids,
|
||||
usernames, display names of subscribers — must not reach the evidence store
|
||||
in any form, redacted or otherwise. There is no legitimate analysis that
|
||||
needs them, so the safe rule is that they never arrive.
|
||||
- **Entry content is not telemetry.** An entry body may appear in a request; it
|
||||
must not appear in a telemetry event. What is recorded is its length, its
|
||||
section count, and whether conversion succeeded.
|
||||
- **The bot token appears nowhere.** Not in errors, not in traces, not in
|
||||
redacted form.
|
||||
|
||||
## The pseudonymization salt
|
||||
|
||||
Generate once, store in OpenBao alongside the bot token, inject as
|
||||
`FLUID_REDACTION_SALT`:
|
||||
|
||||
```bash
|
||||
openssl rand -hex 32
|
||||
```
|
||||
|
||||
It must never change. Rotating it makes every previously-seen consumer look
|
||||
new, which silently invalidates every longitudinal comparison the interface has
|
||||
accumulated. If it is ever compromised, the correct response is to accept the
|
||||
loss and start a fresh evidence baseline, not to rotate quietly.
|
||||
|
||||
## Metrics
|
||||
|
||||
| Metric | Role | Source |
|
||||
|---|---|---|
|
||||
| `read_through_rate` | primary | Telegram channel statistics, aggregate |
|
||||
| `messages_per_entry` | guardrail | Adapter |
|
||||
| `entry_content_loss` | guardrail | Adapter |
|
||||
| `error_rate` | guardrail | Gateway |
|
||||
| `reaction_rate` | secondary | Telegram channel statistics, aggregate |
|
||||
| `time_to_first_read` | learning | Telegram channel statistics, aggregate |
|
||||
|
||||
`read_through_rate` is the one to be careful about. Telegram reports view counts
|
||||
per message, not per reader. For the teaser form it is link clicks over views;
|
||||
for the serialized form it is views of the last part over views of the first.
|
||||
Those are not the same measurement, and comparing them directly is the weakest
|
||||
part of the first experiment. Say so in the experiment's amendment record rather
|
||||
than letting the comparison look cleaner than it is.
|
||||
76
docs/runbook.md
Normal file
76
docs/runbook.md
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
# Runbook — helix-forge-telegram-publishing
|
||||
|
||||
## Rotating the bot token
|
||||
|
||||
1. In BotFather: `/revoke` for the bot, which issues a new token.
|
||||
2. Write the new token to OpenBao at the interface's secret path.
|
||||
3. Restart the adapter. It reads the token at start only.
|
||||
4. Confirm with one publication to the **private test channel**.
|
||||
|
||||
The old token stops working the moment it is revoked, so publications in flight
|
||||
will fail. Do this when nothing is publishing.
|
||||
|
||||
## Recovering channel administration
|
||||
|
||||
If the bot loses its administrator rights, it cannot post and every publication
|
||||
fails with a Telegram `400`.
|
||||
|
||||
1. In the channel's administrator settings, confirm the bot is present with
|
||||
**Post Messages** enabled.
|
||||
2. Grant nothing else. The bot does not need to edit others' messages, delete
|
||||
messages, or manage members, and the intent forbids it from using such
|
||||
rights if it had them.
|
||||
3. If the bot was removed entirely, re-add it and re-run one test publication.
|
||||
|
||||
A human must do this. The Daimon is forbidden from changing channel membership
|
||||
or permissions (`InterfaceEvolutionIntent.md` §7).
|
||||
|
||||
## Rolling back a revision
|
||||
|
||||
Publish a routing policy with a higher generation naming the previous revision
|
||||
as default:
|
||||
|
||||
```bash
|
||||
fluid experiment stop <experiment-id> --generation N --default-revision R-1 \
|
||||
--reason "..." --policy-out rollback.json
|
||||
fluid policy put --file rollback.json
|
||||
```
|
||||
|
||||
If no experiment is running, write the policy by hand with `default_revision`
|
||||
set to the known-good revision and a generation above the current one. Rollback
|
||||
is a document replacement; nothing is unwound.
|
||||
|
||||
Already-published Telegram messages are **not** rolled back. They were seen.
|
||||
Correcting a published entry is an edit through the adapter, not a rollback.
|
||||
|
||||
## An entry published wrongly
|
||||
|
||||
1. Stop further publication: install a policy pointing at the last known-good
|
||||
revision.
|
||||
2. Correct the entry in `hall-of-helix` and re-publish it. The adapter edits the
|
||||
existing messages rather than posting new ones.
|
||||
3. If the entry cannot be corrected in place — wrong entry entirely, or content
|
||||
that should not have been published — delete the messages by hand in
|
||||
Telegram and record why in the publication's audit trail.
|
||||
4. Record a pressure observation. An entry published wrongly is exactly the
|
||||
evidence the framework exists to collect, and the instinct to tidy it away
|
||||
is the instinct to lose it.
|
||||
|
||||
## Checking what happened
|
||||
|
||||
```bash
|
||||
export FLUID_INTERFACE=helix-forge-telegram-publishing
|
||||
|
||||
fluid audit trace R-1 # how a revision came to exist, and what followed
|
||||
fluid pressure list # what the interface is currently struggling with
|
||||
fluid telemetry --limit 50 # recent traffic
|
||||
fluid events --entity <entry-id> # one publication's history
|
||||
```
|
||||
|
||||
## When the evidence store is unavailable
|
||||
|
||||
Publication continues. The gateway serves from cached configuration and buffers
|
||||
telemetry, dropping it if the buffer fills. You lose observation, not service.
|
||||
|
||||
Do not promote anything while the store is down: the promotion would be
|
||||
unauditable, which is the one thing this framework will not trade away.
|
||||
68
experiments/E-tg-length.yaml
Normal file
68
experiments/E-tg-length.yaml
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# The first live experiment: which presentation serves readers better.
|
||||
#
|
||||
# Do not start this until the private test channel has confirmed both forms
|
||||
# render correctly, and the public channel has enough subscribers for the
|
||||
# result to mean anything. The intent (section 12) is explicit that an
|
||||
# underpowered window should report INCONCLUSIVE rather than be shortened.
|
||||
#
|
||||
# fluid experiment design --file experiments/E-tg-length.yaml
|
||||
# fluid experiment start E-tg-length --generation 2 \
|
||||
# --default-revision R-2 --policy-out rp.json
|
||||
# fluid policy put --file rp.json
|
||||
fluid_experiment:
|
||||
schema_version: "0.1"
|
||||
|
||||
id: "E-tg-length"
|
||||
interface_id: "helix-forge-telegram-publishing"
|
||||
|
||||
hypothesis_refs:
|
||||
- "H-tg-teaser"
|
||||
- "H-tg-serial"
|
||||
|
||||
# R-2 implements the teaser form, R-3 the serialized form. Neither is the
|
||||
# control in the usual sense: R-1 cannot publish a long entry at all, so
|
||||
# this is a comparison between two answers rather than against a baseline.
|
||||
control_revision: "R-2"
|
||||
candidate_revisions:
|
||||
- "R-3"
|
||||
|
||||
cohorts:
|
||||
- "telegram-subscribers"
|
||||
|
||||
allocation:
|
||||
control: 0.5
|
||||
candidate: 0.5
|
||||
|
||||
# An even split because there is no incumbent to protect. The usual 90/10
|
||||
# caution exists to limit exposure to an unproven candidate; here both arms
|
||||
# are equally unproven and the scarce resource is entries, not safety.
|
||||
|
||||
metrics:
|
||||
primary:
|
||||
- "read_through_rate"
|
||||
guardrails:
|
||||
- "entry_content_loss"
|
||||
- "error_rate"
|
||||
- "messages_per_entry"
|
||||
secondary:
|
||||
- "reaction_rate"
|
||||
- "forward_rate"
|
||||
learning:
|
||||
- "time_to_first_read"
|
||||
|
||||
start_conditions:
|
||||
- "both revisions verified against the private test channel"
|
||||
- "channel has at least 100 subscribers"
|
||||
|
||||
stop_conditions:
|
||||
- "hard_guardrail_violation"
|
||||
- "entry_content_loss"
|
||||
- "manual_stop"
|
||||
- "max_duration_reached"
|
||||
|
||||
max_duration_hours: 2160 # 90 days: roughly 12 entries at current cadence
|
||||
|
||||
result:
|
||||
state: "PLANNED"
|
||||
preferred_revision: null
|
||||
evidence_refs: []
|
||||
125
hypotheses/H-tg-serial.yaml
Normal file
125
hypotheses/H-tg-serial.yaml
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
# Competing hypothesis B, in group CG-tg-length.
|
||||
fluid_hypothesis:
|
||||
schema_version: "0.1"
|
||||
|
||||
id: "H-tg-serial"
|
||||
interface_id: "helix-forge-telegram-publishing"
|
||||
state: "DRAFT"
|
||||
|
||||
title: "The whole entry in the channel serves readers better than a link"
|
||||
|
||||
observation:
|
||||
summary: >
|
||||
85% of hall entries exceed Telegram's 4096-character message limit, with
|
||||
a median body of 5767 characters. The interface must choose a form for
|
||||
long entries before it can publish at all.
|
||||
evidence_refs:
|
||||
- "measurement:hall-of-helix/entries@2026-09-04"
|
||||
- "pressure:P-tg-length"
|
||||
affected_cohorts:
|
||||
- "telegram-subscribers"
|
||||
|
||||
pressure:
|
||||
classes:
|
||||
- "missing_interface_capability"
|
||||
pressure_refs:
|
||||
- "P-tg-length"
|
||||
severity: 0.85
|
||||
confidence: 1.0
|
||||
|
||||
explanation:
|
||||
claim: >
|
||||
A link out of Telegram is a cost most readers will not pay. Telegram's
|
||||
rich-text posts are built for long-form reading and the audience is
|
||||
already there. Serializing the entry across a small number of linked
|
||||
messages keeps the whole account where the reader is, and the median
|
||||
entry needs only two.
|
||||
reach:
|
||||
score: 0.5
|
||||
explains:
|
||||
- "P-tg-length"
|
||||
notes: >
|
||||
Medium reach. It answers the length constraint natively but does not
|
||||
transfer to platforms with harder limits.
|
||||
|
||||
proposed_adaptation:
|
||||
class: "presentation"
|
||||
summary: >
|
||||
Split the entry at section boundaries into a small number of rich-text
|
||||
messages, each linked to the previous, with the visual attached to the
|
||||
first.
|
||||
implementation_scope: "interface_only"
|
||||
|
||||
backend_requirements:
|
||||
required: false
|
||||
requirement_refs: []
|
||||
|
||||
expected_outcomes:
|
||||
- metric: "read_through_rate"
|
||||
cohort: "telegram-subscribers"
|
||||
target: 0.15
|
||||
direction: "higher"
|
||||
- metric: "messages_per_entry"
|
||||
cohort: "all"
|
||||
baseline: 1.0
|
||||
target: 2.0
|
||||
direction: "higher"
|
||||
|
||||
guardrails:
|
||||
- metric: "entry_content_loss"
|
||||
operator: "=="
|
||||
threshold: false
|
||||
- metric: "error_rate"
|
||||
operator: "<="
|
||||
threshold: 0.01
|
||||
- metric: "messages_per_entry"
|
||||
operator: "<="
|
||||
threshold: 4
|
||||
|
||||
fitness_dimensions:
|
||||
expected:
|
||||
client_utility: 0.5
|
||||
discoverability: 0.3
|
||||
simplicity: -0.2
|
||||
correctness: 0.0
|
||||
security: 0.0
|
||||
|
||||
complexity:
|
||||
expected_delta:
|
||||
operation_count: 0
|
||||
concept_count: 1
|
||||
alternative_path_count: 1
|
||||
cognitive_load: 0.2
|
||||
score: 0.3
|
||||
|
||||
risk:
|
||||
level: "LOW"
|
||||
reasons:
|
||||
- "presentation only"
|
||||
- "splitting can lose meaning at a bad boundary; guarded by content-loss check"
|
||||
|
||||
economics:
|
||||
estimated_experiment_cost: 0.0
|
||||
estimated_implementation_cost: 0.0
|
||||
currency: "EUR"
|
||||
expected_value_class: "MEDIUM"
|
||||
|
||||
learning_value:
|
||||
score: 0.8
|
||||
notes: >
|
||||
Tests whether an audience will read long-form in place, which is the
|
||||
question behind every publishing interface HelixForge will build.
|
||||
|
||||
success_criteria:
|
||||
expression: >
|
||||
read_through_rate >= 0.15 AND messages_per_entry <= 4 AND no guardrail violation
|
||||
|
||||
failure_criteria:
|
||||
expression: >
|
||||
any hard guardrail violation
|
||||
OR read_through_rate below the teaser alternative over the same window
|
||||
|
||||
outcome:
|
||||
status: null
|
||||
summary: null
|
||||
evidence_refs: []
|
||||
125
hypotheses/H-tg-teaser.yaml
Normal file
125
hypotheses/H-tg-teaser.yaml
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
# Competing hypothesis A, in group CG-tg-length.
|
||||
#
|
||||
# Create with:
|
||||
# fluid hypothesis create --file hypotheses/H-tg-teaser.yaml
|
||||
# fluid hypothesis compete --group CG-tg-length H-tg-teaser H-tg-serial
|
||||
fluid_hypothesis:
|
||||
schema_version: "0.1"
|
||||
|
||||
id: "H-tg-teaser"
|
||||
interface_id: "helix-forge-telegram-publishing"
|
||||
state: "DRAFT"
|
||||
|
||||
title: "A teaser and a link serve readers better than the whole entry"
|
||||
|
||||
observation:
|
||||
summary: >
|
||||
85% of hall entries exceed Telegram's 4096-character message limit, with
|
||||
a median body of 5767 characters. The interface must choose a form for
|
||||
long entries before it can publish at all.
|
||||
evidence_refs:
|
||||
- "measurement:hall-of-helix/entries@2026-09-04"
|
||||
- "pressure:P-tg-length"
|
||||
affected_cohorts:
|
||||
- "telegram-subscribers"
|
||||
|
||||
pressure:
|
||||
classes:
|
||||
- "missing_interface_capability"
|
||||
pressure_refs:
|
||||
- "P-tg-length"
|
||||
severity: 0.85
|
||||
confidence: 1.0
|
||||
|
||||
explanation:
|
||||
claim: >
|
||||
A channel post is scanned, not read. A hall entry is a considered
|
||||
first-person account that rewards attention. Presenting an opening and a
|
||||
link lets a reader decide to give it that attention somewhere designed
|
||||
for reading, rather than committing to a wall of text in a feed.
|
||||
reach:
|
||||
score: 0.5
|
||||
explains:
|
||||
- "P-tg-length"
|
||||
notes: >
|
||||
Medium reach. It answers the length constraint and would answer the
|
||||
same constraint on X, but it does not address entries with visuals or
|
||||
entries readers might want to keep in the channel.
|
||||
|
||||
proposed_adaptation:
|
||||
class: "presentation"
|
||||
summary: >
|
||||
Publish the title, the display name, the opening of the entry, and a link
|
||||
to the full entry in hall-of-helix.
|
||||
implementation_scope: "interface_only"
|
||||
|
||||
backend_requirements:
|
||||
required: false
|
||||
requirement_refs: []
|
||||
|
||||
expected_outcomes:
|
||||
- metric: "read_through_rate"
|
||||
cohort: "telegram-subscribers"
|
||||
target: 0.15
|
||||
direction: "higher"
|
||||
- metric: "messages_per_entry"
|
||||
cohort: "all"
|
||||
baseline: 2.0
|
||||
target: 1.0
|
||||
direction: "lower"
|
||||
|
||||
guardrails:
|
||||
- metric: "entry_content_loss"
|
||||
operator: "=="
|
||||
threshold: false
|
||||
- metric: "error_rate"
|
||||
operator: "<="
|
||||
threshold: 0.01
|
||||
|
||||
fitness_dimensions:
|
||||
expected:
|
||||
client_utility: 0.4
|
||||
discoverability: 0.5
|
||||
simplicity: 0.6
|
||||
correctness: 0.0
|
||||
security: 0.0
|
||||
|
||||
complexity:
|
||||
expected_delta:
|
||||
operation_count: 0
|
||||
concept_count: 0
|
||||
cognitive_load: -0.1
|
||||
score: 0.05
|
||||
|
||||
risk:
|
||||
level: "LOW"
|
||||
reasons:
|
||||
- "presentation only; no contract or authorization change"
|
||||
- "fully reversible by replacing the revision"
|
||||
|
||||
economics:
|
||||
estimated_experiment_cost: 0.0
|
||||
estimated_implementation_cost: 0.0
|
||||
currency: "EUR"
|
||||
expected_value_class: "MEDIUM"
|
||||
|
||||
learning_value:
|
||||
score: 0.8
|
||||
notes: >
|
||||
Whichever way this resolves, the answer transfers directly to
|
||||
fluid-x and fluid-substack, which face the same problem under
|
||||
different constraints.
|
||||
|
||||
success_criteria:
|
||||
expression: >
|
||||
read_through_rate >= 0.15 AND no guardrail violation
|
||||
|
||||
failure_criteria:
|
||||
expression: >
|
||||
any hard guardrail violation
|
||||
OR read_through_rate below the serialized alternative over the same window
|
||||
|
||||
outcome:
|
||||
status: null
|
||||
summary: null
|
||||
evidence_refs: []
|
||||
47
pressure/P-length-limit.yaml
Normal file
47
pressure/P-length-limit.yaml
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# Seed pressure record.
|
||||
#
|
||||
# This is not derived from telemetry. It is a structural constraint measured
|
||||
# directly against hall-of-helix on 2026-09-04, and it is real before the
|
||||
# interface has served a single request. The intent (section 13) permits a
|
||||
# structural constraint to found a hypothesis without waiting for repetition.
|
||||
#
|
||||
# Record it with:
|
||||
# fluid pressure ... (or let the first classification pass confirm it)
|
||||
fluid_pressure:
|
||||
schema_version: "0.1"
|
||||
|
||||
id: "P-tg-length"
|
||||
interface_id: "helix-forge-telegram-publishing"
|
||||
|
||||
class: "missing_interface_capability"
|
||||
|
||||
first_seen: "2026-09-04T00:00:00Z"
|
||||
last_seen: "2026-09-04T00:00:00Z"
|
||||
|
||||
affected_cohorts:
|
||||
- "hall-publishing-jobs"
|
||||
- "helix-forge-agents"
|
||||
|
||||
frequency:
|
||||
observations: 80
|
||||
independent_consumers: 2
|
||||
|
||||
severity: 0.85
|
||||
confidence: 1.0
|
||||
|
||||
summary: >
|
||||
Telegram limits a single message to 4096 characters. Of the 94 entries in
|
||||
hall-of-helix on 2026-09-04, 80 have bodies exceeding that limit — 85%.
|
||||
The median body is 5767 characters and the largest is 10220. No entry of
|
||||
typical length can be published as one message, so the interface cannot
|
||||
fulfil its mission without deciding how a long entry becomes a post.
|
||||
|
||||
evidence_refs:
|
||||
- "measurement:hall-of-helix/entries@2026-09-04"
|
||||
- "canon:fluid-telegram/TelegramCapabilityCanon.md#PUB-02"
|
||||
|
||||
status: "ANALYZING"
|
||||
|
||||
linked_hypotheses:
|
||||
- "H-tg-teaser"
|
||||
- "H-tg-serial"
|
||||
52
revisions/R-1.yaml
Normal file
52
revisions/R-1.yaml
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# Revision descriptor for R-1.
|
||||
#
|
||||
# Fill in the digest and upstream, then publish through the pipeline:
|
||||
#
|
||||
# fluid revision publish --file revisions/R-1.yaml \
|
||||
# --key-file "$FLUID_SIGNING_KEY" \
|
||||
# --adaptation-classes presentation \
|
||||
# --approved-by "$USER" --traffic-share 1.0
|
||||
#
|
||||
# The digest is the sha256 of contracts/r1.openapi.yaml:
|
||||
# printf 'sha256:%s' "$(sha256sum contracts/r1.openapi.yaml | cut -d' ' -f1)"
|
||||
revision:
|
||||
schema_version: "0.1"
|
||||
id: "R-1"
|
||||
interface: "helix-forge-telegram-publishing"
|
||||
state: "stable"
|
||||
|
||||
contract:
|
||||
type: "openapi"
|
||||
digest: "sha256:<digest of contracts/r1.openapi.yaml>"
|
||||
source: "contracts/r1.openapi.yaml"
|
||||
|
||||
runtime:
|
||||
upstream: "http://fluid-telegram-adapter:8080"
|
||||
# Telegram rate-limits channel posts to roughly 20 per minute. A generous
|
||||
# timeout is correct here: publication is not latency-sensitive, and
|
||||
# failing fast would turn a rate limit into a lost entry.
|
||||
timeout_ms: 30000
|
||||
retry:
|
||||
# Only GET is retried by the connector; POST /v1/hall-entries is
|
||||
# idempotent on entry_id, but the retry decision stays with the adapter,
|
||||
# which knows whether Telegram accepted the message.
|
||||
max_attempts: 1
|
||||
circuit_breaker:
|
||||
failure_threshold: 5
|
||||
reset_after_ms: 60000
|
||||
|
||||
intent:
|
||||
version: "IEI-1"
|
||||
|
||||
routing:
|
||||
eligible_cohorts:
|
||||
- "hall-publishing-jobs"
|
||||
- "helix-forge-agents"
|
||||
- "operators"
|
||||
max_traffic_share: 1.0
|
||||
|
||||
policy:
|
||||
compatibility: "additive"
|
||||
security_check: "passed"
|
||||
policy_check: "passed"
|
||||
rollback_to: null
|
||||
190
workplans/FT-WP-0001-telegram-identity-and-hall-channel.md
Normal file
190
workplans/FT-WP-0001-telegram-identity-and-hall-channel.md
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
---
|
||||
id: FT-WP-0001
|
||||
type: workplan
|
||||
title: "Establish HelixForge's Telegram identity and publish the Hall of Helix"
|
||||
domain: infotech
|
||||
repo: fluid-telegram
|
||||
status: active
|
||||
owner: worsch
|
||||
topic_slug: fluid-telegram
|
||||
created: "2026-09-04"
|
||||
updated: "2026-09-04"
|
||||
planning_priority: high
|
||||
planning_order: 1
|
||||
depends_on:
|
||||
- FLUID-WP-0008
|
||||
related_repos:
|
||||
- fluid-core
|
||||
- helix-forge
|
||||
- hall-of-helix
|
||||
---
|
||||
|
||||
# FT-WP-0001 — Telegram identity and the Hall of Helix channel
|
||||
|
||||
Establish a Telegram identity for HelixForge and a channel publishing
|
||||
hall-of-helix entries, as the first real FLUID interface.
|
||||
|
||||
Everything that could be prepared without credentials has been: the governing
|
||||
intent, the R-1 contract, the adapter's connector contract, the observation
|
||||
configuration, the seed pressure record, and two competing hypotheses for the
|
||||
first experiment. See `docs/` and the handover package at
|
||||
`fluid-core/docs/handover/fluid-telegram.md`.
|
||||
|
||||
What remains needs either a person with a Telegram account or a running
|
||||
adapter.
|
||||
|
||||
## T01 — Register the bot
|
||||
|
||||
```task
|
||||
id: FT-WP-0001-T01
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
**Human step.** Register the HelixForge bot through BotFather (Canon BOT-01).
|
||||
Bots are first-class Telegram accounts and need no phone number.
|
||||
|
||||
Set the bot's name, description and profile image to something a reader
|
||||
encountering it cold would recognise as HelixForge.
|
||||
|
||||
Write the token to OpenBao immediately; do not paste it anywhere else, not even
|
||||
briefly. Consistent with `HF-WP-0002` (bao.coulomb.social).
|
||||
|
||||
## T02 — Create the channels
|
||||
|
||||
```task
|
||||
id: FT-WP-0001-T02
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
**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.
|
||||
|
||||
Every verification below runs against the private channel. Nothing reaches the
|
||||
public channel until the rendering has been seen by a person.
|
||||
|
||||
## T03 — Generate and store the redaction salt
|
||||
|
||||
```task
|
||||
id: FT-WP-0001-T03
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
`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.
|
||||
|
||||
## T04 — Record the intent and publish R-1
|
||||
|
||||
```task
|
||||
id: FT-WP-0001-T04
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
```bash
|
||||
export FLUID_INTERFACE=helix-forge-telegram-publishing
|
||||
fluid intent put --version IEI-1 --file InterfaceEvolutionIntent.md --activate
|
||||
# fill the digest in revisions/R-1.yaml, then
|
||||
fluid revision publish --file revisions/R-1.yaml --key-file "$FLUID_SIGNING_KEY" \
|
||||
--adaptation-classes presentation --approved-by "$USER" --traffic-share 1.0
|
||||
```
|
||||
|
||||
Generate a real signing key pair for this; `--ephemeral-key` is development
|
||||
only, and a revision signed with one stops verifying after a restart.
|
||||
|
||||
## T05 — Build the adapter
|
||||
|
||||
```task
|
||||
id: FT-WP-0001-T05
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
Implement the three operations in `contracts/r1.openapi.yaml` against the
|
||||
connector contract in `docs/adapter-contract.md`. TypeScript with grammY is the
|
||||
natural choice; the wire contract makes the language irrelevant to fluid-core.
|
||||
|
||||
The two requirements that carry the most weight: publication is idempotent on
|
||||
`entry_id`, and an entry that cannot be converted faithfully is refused rather
|
||||
than approximated. `entry_content_loss` is a hard guardrail in both competing
|
||||
hypotheses and can only be honest if the adapter refuses.
|
||||
|
||||
## T06 — Publish one entry to the private channel
|
||||
|
||||
```task
|
||||
id: FT-WP-0001-T06
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
The first real test. Publish a single hall entry to the private channel and
|
||||
confirm, by looking at it:
|
||||
|
||||
- the rendering is faithful and readable;
|
||||
- re-publishing the same entry edits rather than duplicates;
|
||||
- the evidence store contains no Telegram user identity and no bot token;
|
||||
- `fluid audit trace R-1` reconstructs the publication.
|
||||
|
||||
## T07 — Implement both presentation forms as R-2 and R-3
|
||||
|
||||
```task
|
||||
id: FT-WP-0001-T07
|
||||
status: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
R-2 implements `H-tg-teaser`, R-3 implements `H-tg-serial`. Both are
|
||||
presentation adaptations over the same contract, so neither needs a contract
|
||||
change. Verify both against the private channel before either sees a subscriber.
|
||||
|
||||
## T08 — Open the public channel and publish the backlog
|
||||
|
||||
```task
|
||||
id: FT-WP-0001-T08
|
||||
status: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
Point the production adapter at the public channel and publish the existing
|
||||
entries. 94 entries at once would be both a rate-limit problem and an unkind
|
||||
introduction for a new subscriber; publish them oldest-first at a human pace.
|
||||
|
||||
Whichever of R-2 or R-3 is used here becomes the de facto control for the
|
||||
experiment, which is why `E-tg-length` names R-2 as control rather than R-1.
|
||||
|
||||
## T09 — Run the first experiment
|
||||
|
||||
```task
|
||||
id: FT-WP-0001-T09
|
||||
status: todo
|
||||
priority: low
|
||||
```
|
||||
|
||||
Start `E-tg-length` once the channel has roughly 100 subscribers. Before then
|
||||
the evaluator will report INCONCLUSIVE, and that is the correct answer rather
|
||||
than a reason to shorten the window.
|
||||
|
||||
At one entry per week, a 90-day experiment yields about twelve entries. That is
|
||||
thin, and the honest expectation is that the first run is inconclusive and
|
||||
informs a better-powered second one. Record that expectation in the experiment's
|
||||
amendments rather than discovering it as a disappointment.
|
||||
|
||||
## T10 — Close the loop
|
||||
|
||||
```task
|
||||
id: FT-WP-0001-T10
|
||||
status: todo
|
||||
priority: low
|
||||
```
|
||||
|
||||
Record the hypothesis outcome, resolve the competition group, and promote the
|
||||
winner. Then confirm `fluid audit trace` reconstructs the whole chain from the
|
||||
length-limit pressure through to the promoted revision.
|
||||
|
||||
That trace is the deliverable. It is what makes this the first FLUID interface
|
||||
rather than a bot that posts to a channel.
|
||||
Loading…
Add table
Add a link
Reference in a new issue